Come determinare cosa dovrebbe ottenere il proprio controller?


10

Sto usando il modello MVC nella mia applicazione web creata con PHP.

Faccio sempre fatica a determinare se ho bisogno di un nuovo controller dedicato per una serie di azioni o se dovrei inserirli in un controller già esistente.

Ci sono delle buone regole pratiche da seguire quando si creano controller?

Ad esempio posso avere:

AuthenticationController con azioni:

  • index() per visualizzare il modulo di accesso.
  • submit() per gestire l'invio dei moduli.
  • logout(), autoesplicativo.

O

LoginController con azioni:

  • index() per visualizzare il modulo di accesso.
  • submit() per gestire l'invio dei moduli.

LogoutController con azione:

  • index() gestire la disconnessione.

O

AccountController con azioni:

  • loginGet() per visualizzare il modulo di accesso.
  • loginPost() per gestire l'invio del modulo di accesso.
  • logoutGet() gestire la disconnessione.
  • registerGet() per visualizzare il modulo di registrazione.
  • registerPost() per gestire l'invio dei moduli.

    E qualsiasi altra azione coinvolta in un account.


Forse dai un'occhiata al design RESTful. Non risolve ogni singolo problema di questo tipo, ma ti dà un'ottima direzione su come pensarci.
Thorsten Müller,

Risposte:


3

Per trovare il giusto raggruppamento per i controller, pensa ai test .

(Anche se in realtà non esegui alcun test, pensare a come faresti per testare i tuoi controller ti darà alcune ottime intuizioni su come strutturarli.)

An AuthenticationControllernon è testabile da solo, poiché contiene solo funzionalità per l'accesso e la disconnessione, ma il codice di test dovrà in qualche modo creare account falsi a scopo di test prima di poter testare un accesso riuscito. Potresti bypassare il sottosistema sotto test e andare direttamente al tuo modello per la creazione degli account di test, ma avrai un test fragile tra le mani: se il modello cambia, dovrai modificare non solo il codice che test il modello, ma anche il codice che testa il controller, anche se l'interfaccia e il comportamento del controller sono rimasti invariati. È irragionevole.

A LoginControllernon è adatto per gli stessi motivi: non è possibile testarlo senza prima creare account e ci sono ancora più cose che non è possibile testare, come ad esempio impedire accessi duplicati ma consentire quindi a un utente di accedere dopo essersi disconnesso. (Poiché questo controller non ha funzionalità di logout.)

Un AccountControllervi darà tutto il necessario per fare il test: è possibile creare un account di prova e poi cercare di login, è possibile eliminare l'account e quindi assicurarsi che non può login più, è possibile modificare la password e assicurarsi che il la password corretta deve essere utilizzata per accedere, ecc.

Per concludere: per scrivere anche la più piccola suite di test, sarà necessario rendere disponibili tutte le funzionalità del pacchetto AccountController. La suddivisione in controller più piccoli sembra produrre controller per disabili con funzionalità insufficiente per un test adeguato. Questa è un'ottima indicazione del fatto che la funzionalità di AccountControllerè la più piccola suddivisione che ha senso.

E in generale, l'approccio "pensa al test" funzionerà non solo in questo particolare scenario, ma in qualsiasi scenario simile che ti imbatterai in futuro.


1

La risposta non è così ovvia

Per favore, vorrei chiarire alcune cose prima di fare qualsiasi dichiarazione di risposta. Prima di tutto:

Cos'è il controller?

Il controller è una parte del sistema che controlla la richiesta - dopo l'invio. Quindi, possiamo definirlo come un insieme di azioni relative a ... che cosa?

Qual è l'ambito del controller?

E questo è più o meno quando avremo una risposta. Cosa ne pensi? È un controllore di cose (ad esempio un Conto) o un controllore di azioni? Ovviamente è un controller di qualche modello o di qualcosa di più astratto che fornisce azioni su di esso.

La risposta è...

AuthenticationController con azioni:

  • index () per visualizzare il modulo di accesso.
  • submit () per gestire l'invio del modulo.
  • logout (), autoesplicativo.

No, l'autenticazione è un processo. Non andare così.

LoginController con azioni:

  • index () per visualizzare il modulo di accesso.
  • submit () per gestire l'invio del modulo.

Anch'io. Login - azione. Meglio non creare un controller di azione (non hai un modello correlato ad esso).

AccountController con azioni:

  • loginGet () per visualizzare il modulo di accesso.
  • loginPost () per gestire l'invio del modulo di accesso.
  • logoutGet () per gestire la disconnessione.
  • registerGet () per visualizzare il modulo di registrazione.
  • registerPost () per gestire l'invio di moduli.

Abbastanza buono, ma non sono convinto che valga la pena costruire quel controller di basso livello (il controller è l'astrazione stessa). Ad ogni modo, la creazione di metodi con * Get o * Post non è chiara.

Qualche suggerimento?

Sì, consideralo:

AccountController:

  • login (AccountModel)
  • Logout (AccountModel)
  • register (AccountModel)
  • indice()

E relativo modello, spesso Classe di account. Ti darà l'opportunità di spostare la tua coppia modello-controller da qualche altra parte (se sarà necessario) e creare un codice chiaro (è ovvio cosa login()significhi il metodo). Stincking to model è davvero famoso soprattutto con le applicazioni CRUD e forse è un modo per te.


1

I controller vengono in genere creati per una determinata risorsa (una classe di entità, una tabella nel database), ma possono anche essere creati per raggruppare azioni responsabili di una determinata parte dell'applicazione. Nei tuoi esempi, sarebbe un controller che gestisce la sicurezza per l'applicazione:

class SecurityController
{
    // can handle both the login page display and
    // the login page submission
    login(); 

    logout();

    register();

    // optional: confirm account after registration
    confirm();

    // displays the forgot password page
    forgotPassword();

    // displays the reset password page
    // and handle the form submission
    resetPassword();
}

Nota : non inserire le azioni relative alla sicurezza e le azioni del profilo utente nello stesso controller; potrebbe avere senso perché sono correlati all'utente, ma uno dovrebbe gestire l'autenticazione e l'altro dovrebbe gestire gli aggiornamenti di e-mail, nome ecc.

Con i controller creati per le risorse (diciamo Task), avresti le solite azioni CRUD :

class TasksController
{
    // usually displays a paginated list of tasks
    index();

    // displays a certain task, based on an identifier
    show(id);

    // displays page with form and
    // handles form submission for creating
    // new tasks
    create();

    // same as create(), but for changing records
    update(id);     

    // displays confirmation message
    // and handles submissions in case of confirmation
    delete()
}

Naturalmente, hai la possibilità di aggiungere risorse correlate allo stesso controller. Supponiamo ad esempio di avere l'entità Businesse ognuna ha diverse BusinessServiceentità. Un controller potrebbe essere simile al seguente:

class BusinessController
{
    index();

    show(id);

    create();

    update(id);

    delete();

    // display the business services for a certain business
    listBusinessServices(businessId);

    // displays a certain business service
    showBusinessService(id);

    // create a new business service for a certain business
    createBusinessService(businessId);

    // updates a certain business service
    updateBusinessService(id);

    // deletes a certain business service
    deleteBusinessService(id);
}

Questo approccio ha senso quando le entità figlio correlate non possono esistere senza l'entità genitore.

Questi sono i miei consigli:

  • creare controller basati su un gruppo di operazioni correlate (gestire determinate responsabilità come la sicurezza o operazioni CRUD su risorse ecc.);
  • per i controller basati su risorse, non aggiungere azioni non necessarie (se non si prevede di aggiornare la risorsa, non aggiungere l'azione di aggiornamento);
  • puoi aggiungere azioni "personalizzate" per semplificare le cose (ad esempio hai Subscriptionun'entità che ha una disponibilità basata su un numero limitato di voci, puoi aggiungere una nuova azione al controller chiamato use()che ha il solo scopo di sottrarre una voce dalla Subscription)
  • mantieni le cose semplici: non ingombrare il tuo controller con un numero enorme di azioni e logiche complesse, cerca di semplificare le cose diminuendo il numero di azioni o realizzando due controller;
  • se stai usando un framework focalizzato su MVC, segui le loro linee guida sulle migliori pratiche (se ce l'hanno).

Alcune risorse per ulteriori letture qui .


0

Vedo due "forze" progettuali antagoniste (che non sono esclusive dei controller):

  • coesione: i responsabili del trattamento dovrebbero raggruppare le azioni correlate
  • semplicità: i controller dovrebbero essere il più piccolo possibile per gestire la loro complessità

Dal punto di vista della coesione, tutte e tre le azioni (login, logout, registrazione) sono correlate, ma login e logout sono molto più che registrazione. Sono collegati semanticamente (l'uno è un'inversione dell'altro) e molto probabilmente useranno anche gli stessi oggetti di servizio (le loro implementazioni sono anche coerenti).

Il mio primo istinto sarebbe quello di raggruppare login e logout in un controller. Ma se le implementazioni del controller di login e logout non sono così semplici (ad es. Login ha captcha, più metodi di autenticazione ecc.), Non avrei problemi a dividerle in LoginController e LogoutController per mantenere la semplicità. Dove si trova questa soglia di complessità (quando dovresti iniziare a dividere il controller) è un po 'personale.

Ricorda inoltre che qualunque cosa tu abbia progettato il tuo codice inizialmente, puoi (e dovresti) rifattorizzarlo mentre cambia. In questo caso è abbastanza tipico iniziare con un design semplice (avere un AuthenticationController) e con il tempo riceverai più requisiti che complicheranno il codice. Una volta varcata la soglia di complessità, è necessario riformattarla su due controller.

A proposito, il tuo codice suggerisce che stai disconnettendo l'utente con la richiesta GET. Questa è una cattiva idea poiché HTTP GET dovrebbe essere nullipotent (non dovrebbe modificare lo stato dell'applicazione).


0

Ecco alcune regole pratiche:

  • Organizza per argomento o argomento, con il nome del controller come nome dell'argomento.

  • Ricorda che il nome del controller apparirà nell'URL, visibile ai tuoi utenti, quindi preferibilmente dovrebbe avere senso per loro.

Nella situazione menzionata (autenticazione) il team MVC ha già scritto il controller per te. Apri Visual Studio 2013 e fai clic su

File / New / Project... 
Search installed templates for "ASP.NET MVC4 Web Application"
Choose "Internet Application" / OK.

AccountController.cs contiene tutti i metodi per la gestione degli account utente:

Login()
Logoff()
Register()
Disassociate()
Manage()
ExternalLogin()

Così hanno organizzato per argomento "Account utente e autenticazione", con il nome dell'argomento visibile "Account".


0

Terminologia

Credo che sia un'idea sbagliata chiamare una classe contenente alcuni metodi relativi a HTTP un "controller".

Il controller è un metodo che gestisce la richiesta, ma non una classe contenente tali metodi . Così, index(), submit(), logout()sono i controllori.

La classe che contiene quel tipo di metodi è denominata "controller" solo perché costituisce un gruppo di controller e svolge un ruolo di spazio dei nomi "di livello inferiore". In linguaggio FP (come Haskell) sarebbe solo un modulo. È buona norma mantenere le classi di "controllori" il più apolidi possibile nei linguaggi OOP, ad eccezione dei riferimenti a servizi e altre cose a livello di programma.

La risposta

Con la terminologia risolta, la domanda è "come dovremmo separare i controller in spazi dei nomi / moduli?" Penso che la risposta sia: i controller all'interno di un singolo spazio dei nomi / modulo dovrebbero gestire lo stesso tipo di dati . Ad esempio, si UserControlleroccupa principalmente di istanze di Userclasse, ma occasionalmente tocca altre cose correlate, se necessario.

Dal momento che login, logoute altre azioni simili riguardano principalmente la sessione, probabilmente è meglio inserirle all'interno SessionControllere il indexcontroller, che stampa solo un modulo, deve essere inserito LoginPageController, poiché si occupa ovviamente della pagina di accesso. Ha poco senso mettere il rendering HTML e la gestione delle sessioni in una singola classe, e ciò violerebbe SRP e probabilmente un sacco di altre buone pratiche.

Principio generale

Quando hai difficoltà a decidere dove inserire un pezzo di codice, inizia con i dati (e i tipi) che hai a che fare.


2
Siamo spiacenti, queste sono Azioni, non Controller :)
JK01

@ JK01 Questi sono quelli che li chiami. È una terminologia, lo sai. E ci sono framework che chiamano queste funzioni "controller" (o "gestori"), dal momento che ci sono molti framework che non li organizzano in classi, poiché gli spazi dei nomi / moduli sono già sufficienti. Puoi usare tutti i termini che ti piacciono, sono solo parole, ma penso che avere meno termini sia meglio.
scriptin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.