Wie kann man bestimmen, was einen eigenen Controller bekommen soll?


10

Ich verwende das MVC-Muster in meiner mit PHP erstellten Webanwendung.

Ich habe immer Probleme zu bestimmen, ob ich einen neuen dedizierten Controller für eine Reihe von Aktionen benötige oder ob ich sie in einem bereits vorhandenen Controller platzieren sollte.

Gibt es gute Faustregeln beim Erstellen von Controllern?

Zum Beispiel kann ich haben:

AuthenticationController mit Aktionen:

  • index() um das Anmeldeformular anzuzeigen.
  • submit() Formularübermittlung zu behandeln.
  • logout(), selbsterklärend.

ODER

LoginController mit Aktionen:

  • index() um das Anmeldeformular anzuzeigen.
  • submit() Formularübermittlung zu behandeln.

LogoutController mit Aktion:

  • index() um sich abzumelden.

ODER

AccountController mit Aktionen:

  • loginGet() um das Anmeldeformular anzuzeigen.
  • loginPost() um die Übermittlung des Anmeldeformulars zu handhaben.
  • logoutGet() um sich abzumelden.
  • registerGet() um das Anmeldeformular anzuzeigen.
  • registerPost() Formularübermittlung zu behandeln.

    Und alle anderen Aktionen, die mit einem Konto verbunden sind.


Vielleicht werfen Sie einen Blick auf RESTful Design. Es löst nicht jedes einzelne Problem dieser Art, gibt Ihnen aber eine sehr gute Richtung, wie Sie darüber nachdenken sollen.
Thorsten Müller

Antworten:


3

Denken Sie an die Tests , um die richtige Gruppierung für Controller zu finden .

(Selbst wenn Sie keine Tests durchführen, erhalten Sie einige sehr gute Einblicke in die Strukturierung Ihrer Controller, wenn Sie darüber nachdenken, wie Sie Ihre Controller testen würden.)

An kann AuthenticationControllernicht selbst getestet werden, da es nur Funktionen zum An- und Abmelden enthält. Ihr Testcode muss jedoch zu Testzwecken gefälschte Konten erstellen, bevor eine erfolgreiche Anmeldung getestet werden kann. Sie könnten das zu testende Subsystem umgehen und direkt zu Ihrem Modell gehen, um die Testkonten zu erstellen. Dann haben Sie jedoch einen fragilen Test in der Hand: Wenn sich das Modell ändert, müssen Sie nicht nur den Code ändern, der testet das Modell, aber auch Code, der den Controller testet, obwohl die Schnittstelle und das Verhalten des Controllers unverändert geblieben sind. Das ist unvernünftig.

A LoginControllerist aus den gleichen Gründen ungeeignet: Sie können es nicht testen, ohne zuerst Konten zu erstellen, und es gibt noch weitere Dinge, die Sie nicht testen können, z. B. das Verhindern doppelter Anmeldungen, aber das Anmelden eines Benutzers nach dem Abmelden. (Da dieser Controller keine Abmeldefunktion hat.)

An AccountControllergibt Ihnen alles, was Sie für Ihre Tests benötigen: Sie können ein Testkonto erstellen und dann versuchen, sich anzumelden. Sie können das Konto löschen und dann sicherstellen, dass Sie sich nicht mehr anmelden können. Sie können das Passwort ändern und sicherstellen, dass das Für die Anmeldung usw. muss das richtige Passwort verwendet werden.

Fazit: Um auch die kleinste Testsuite zu schreiben, müssen Sie alle Funktionen der AccountControllerverfügbaren Suite zur Verfügung stellen. Die Unterteilung in kleinere Steuerungen scheint zu behinderten Steuerungen mit unzureichender Funktionalität für einen ordnungsgemäßen Test zu führen. Dies ist ein sehr guter Hinweis darauf, dass die Funktionalität von AccountControllerdie kleinste sinnvolle Unterteilung ist.

Und im Allgemeinen funktioniert der Ansatz "Denken Sie an das Testen" nicht nur in diesem speziellen Szenario, sondern in jedem ähnlichen Szenario, auf das Sie in Zukunft stoßen.


1

Die Antwort ist nicht so offensichtlich

Bitte lassen Sie mich einige Dinge klären, bevor ich antwortende Aussagen mache. Zuerst:

Was ist der Controller?

Der Controller ist ein Teil des Systems, der die Anforderung steuert - nach dem Versand. Wir können es also als eine Reihe von Aktionen definieren, die sich auf ... was beziehen?

Was ist der Umfang der Steuerung?

Und das ist mehr oder weniger Teil, wenn wir eine Antwort haben werden. Was denken Sie? Ist es ein Controller von Dingen (zum Beispiel ein Konto) oder ein Controller von Aktionen? Natürlich ist es ein Controller eines Modells oder einer abstrakteren Sache, die Aktionen darauf bereitstellt.

Die Antwort ist...

AuthenticationController mit Aktionen:

  • index (), um das Anmeldeformular anzuzeigen.
  • submit () für die Formularübermittlung.
  • logout (), selbsterklärend.

Nein, Authentifizierung ist ein Prozess. Geh nicht so.

LoginController mit Aktionen:

  • index (), um das Anmeldeformular anzuzeigen.
  • submit () für die Formularübermittlung.

Hier gilt das gleiche. Login - Aktion. Erstellen Sie besser keinen Action-Controller (Sie haben kein korreliertes Modell damit).

AccountController mit Aktionen:

  • loginGet (), um das Anmeldeformular anzuzeigen.
  • loginPost () für die Übermittlung des Anmeldeformulars.
  • logoutGet () zum Abmelden.
  • registerGet (), um das Registrierungsformular anzuzeigen.
  • registerPost () für die Formularübermittlung.

Ziemlich gut, aber ich bin nicht davon überzeugt, dass es sich lohnt, diesen Low-Level-Controller (Controller ist Abstraktion selbst) zu bauen. Wie auch immer, das Erstellen von Methoden mit * Get oder * Post ist unklar.

Irgendein Vorschlag?

Ja, bedenken Sie es:

AccountController:

  • Login (AccountModel)
  • Abmelden (AccountModel)
  • registrieren (AccountModel)
  • Index()

Und verwandtes Modell dazu, ofc Account-Klasse. Sie haben die Möglichkeit, Ihr Modell-Controller-Paar an einen anderen Ort zu verschieben (falls erforderlich) und einen klaren Code zu erstellen (es ist offensichtlich, was die login()Methode bedeutet). Das Modellieren ist besonders bei CRUD-Anwendungen sehr beliebt, und vielleicht ist es ein Weg für Sie.


1

Controller werden normalerweise für eine bestimmte Ressource (eine Entitätsklasse, eine Tabelle in der Datenbank) erstellt, können aber auch erstellt werden, um Aktionen zu gruppieren, die für einen bestimmten Teil der Anwendung verantwortlich sind. In Ihren Beispielen wäre dies ein Controller, der die Sicherheit für die Anwendung übernimmt:

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();
}

Hinweis : Legen Sie die sicherheitsrelevanten Aktionen und die Benutzerprofilaktionen nicht in demselben Controller ab. Dies ist möglicherweise sinnvoll, da sie sich auf den Benutzer beziehen. Einer sollte jedoch die Authentifizierung und der andere die Aktualisierung von E-Mails, Namen usw. übernehmen.

Mit Controllern, die für Ressourcen erstellt wurden (sagen wir Task), hätten Sie die üblichen CRUD- Aktionen:

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()
}

Natürlich haben Sie die Möglichkeit, dem gleichen Controller verwandte Ressourcen hinzuzufügen. Angenommen, Sie haben die Entität Businessund jede hat mehrere BusinessServiceEntitäten. Ein Controller dafür könnte folgendermaßen aussehen:

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);
}

Dieser Ansatz ist sinnvoll, wenn die zugehörigen untergeordneten Entitäten ohne die übergeordnete Entität nicht existieren können.

Dies sind meine Empfehlungen:

  • Erstellen von Controllern basierend auf einer Gruppe verwandter Vorgänge (Umgang mit bestimmten Verantwortlichkeiten wie Sicherheit oder CRUD-Vorgängen für Ressourcen usw.);
  • Fügen Sie bei ressourcenbasierten Controllern keine unnötigen Aktionen hinzu (wenn Sie die Ressource nicht aktualisieren sollen, fügen Sie die Aktualisierungsaktion nicht hinzu).
  • Sie können "benutzerdefinierte" Aktionen hinzufügen, um die Dinge zu vereinfachen (z. B. wenn Sie eine SubscriptionEntität haben, deren Verfügbarkeit auf einer begrenzten Anzahl von Einträgen basiert, können Sie dem genannten Controller eine neue Aktion hinzufügen use(), die den einzigen Zweck hat, einen Eintrag von der zu subtrahieren Subscription).
  • Halten Sie die Dinge einfach - überladen Sie Ihren Controller nicht mit einer großen Anzahl von Aktionen und komplexer Logik. Versuchen Sie, die Dinge zu vereinfachen, indem Sie die Anzahl der Aktionen verringern oder zwei Controller erstellen.
  • Wenn Sie ein MVC-fokussiertes Framework verwenden, befolgen Sie die Best-Practice-Richtlinien (sofern vorhanden).

Einige Ressourcen zur weiteren Lektüre hier .


0

Ich sehe zwei antagonistische Design- "Kräfte" (die nicht nur für Controller gelten):

  • Kohäsivität - Controller sollten verwandte Aktionen gruppieren
  • Einfachheit - Controller sollten so klein wie möglich sein, um ihre Komplexität zu verwalten

Unter dem Gesichtspunkt der Kohäsivität hängen alle drei Aktionen (Anmelden, Abmelden, Registrierung) zusammen, aber Anmelden und Abmelden sind viel mehr als Registrierung. Sie sind semantisch miteinander verbunden (eines ist eine Inversion des anderen) und verwenden möglicherweise auch dieselben Serviceobjekte (ihre Implementierungen sind ebenfalls zusammenhängend).

Mein erster Instinkt wäre, die Anmeldung und Abmeldung in einem Controller zu gruppieren. Aber wenn die Implementierungen von Login & Logout-Controllern nicht so einfach sind (z. B. Login hat Captcha, mehr Authentifizierungsmethoden usw.), hätte ich kein Problem, sie zur Vereinfachung in LoginController und LogoutController zu unterteilen. Wo diese Schwelle der Komplexität liegt (wann Sie mit der Aufteilung des Controllers beginnen sollten), ist ein bisschen persönlich.

Denken Sie auch daran, dass Sie Ihren Code, was auch immer Sie anfangs entwerfen, bei Änderungen ändern können (und sollten). In diesem Fall ist es recht typisch, mit einem einfachen Design zu beginnen (einen AuthenticationController zu haben), und mit der Zeit erhalten Sie weitere Anforderungen, die den Code komplizieren. Sobald der Komplexitätsschwellenwert überschritten ist, sollten Sie ihn auf zwei Controller umgestalten.

Übrigens schlägt Ihr Code vor, dass Sie Benutzer mit GET-Anforderung abmelden. Das ist eine schlechte Idee, da HTTP GET nullipotent sein sollte (es sollte den Status der Anwendung nicht ändern).


0

Hier einige Faustregeln:

  • Organisieren Sie nach Thema oder Thema, wobei der Controller-Name der Name des Themas ist.

  • Denken Sie daran, dass der Name des Controllers in der URL angezeigt wird, die für Ihre Benutzer sichtbar ist. Daher sollte dies für sie vorzugsweise sinnvoll sein.

In der von Ihnen erwähnten Situation (Authentifizierung) hat das MVC-Team den Controller bereits für Sie geschrieben. Öffnen Sie Visual Studio 2013 und klicken Sie dann auf

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

AccountController.cs enthält alle Methoden zum Verwalten von Benutzerkonten:

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

Sie haben also nach Thema "Benutzerkonten und Authentifizierung" mit dem sichtbaren Themennamen "Konto" organisiert.


0

Terminologie

Ich glaube, es ist ein großes Missverständnis, eine Klasse, die einige HTTP-bezogene Methoden enthält, als "Controller" zu bezeichnen.

Controller ist eine Methode, die Anforderungen verarbeitet, jedoch keine Klasse, die solche Methoden enthält . Also index(), submit(), logout()sind Controller.

Eine Klasse, die diese Art von Methoden enthält, wird als "Controller" bezeichnet, nur weil sie eine Gruppe von Controllern darstellt und eine Rolle als "unterster" Namespace spielt. In der FP-Sprache (wie Haskell) wäre es nur ein Modul. Es wird empfohlen, diese "Controller" -Klassen in OOP-Sprachen so zustandslos wie möglich zu halten, mit Ausnahme von Verweisen auf Dienste und andere programmweite Inhalte.

Die Antwort

Wenn die Terminologie aussortiert ist, lautet die Frage: "Wie sollen wir Controller in Namespaces / Module unterteilen?" Ich denke, die Antwort lautet: Controller in einem einzelnen Namespace / Modul sollten mit der gleichen Art von Daten umgehen . Zum Beispiel UserControllerbeschäftigt sich in erster Linie mit Instanzen der UserKlasse, aber gelegentlich berührt andere ähnliche Dinge, falls erforderlich.

Da login, logoutund andere solche Aktionen sind meist mit der Sitzung zu tun, ist es wahrscheinlich am besten, legt sie im Innern SessionControllerund indexController, der nur ein Formular druckt, soll in platziert werden LoginPageController, da es offensichtlich mit Login - Seite beschäftigt. Es ist ein wenig sinnvoll, HTML-Rendering und Sitzungsverwaltung in einer einzigen Klasse zusammenzufassen. Dies würde gegen SRP und wahrscheinlich gegen eine Reihe anderer bewährter Methoden verstoßen .

Allgemeines Prinzip

Wenn Sie Probleme bei der Entscheidung haben, wo ein Code abgelegt werden soll, beginnen Sie mit den Daten (und Typen), mit denen Sie sich befassen.


2
Sorry, das sind Aktionen, keine Controller :)
JK01

@ JK01 So nennt man sie. Es ist Terminologie, wissen Sie. Und es gibt Frameworks, die diese Funktionen "Controller" (oder "Handler") nennen, da es viele Frameworks gibt, die diese nicht in Klassen organisieren, da Namespaces / Module bereits ausreichen. Sie können beliebige Begriffe verwenden, es sind nur Wörter, aber ich denke, weniger Begriffe sind besser.
Scriptin
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.