Frage

Ich schreibe derzeit eine ASP.Net-App von der Benutzeroberfläche aus.Ich implementieren eine MVP-Architektur, weil ich Winforms satt habe und etwas wollte, das eine bessere Trennung von Belangen bietet.

Bei MVP verarbeitet der Presenter also Ereignisse, die von der Ansicht ausgelöst werden.Hier ist ein Code, den ich für die Erstellung von Benutzern habe:

public class CreateMemberPresenter
{
    private ICreateMemberView view;
    private IMemberTasks tasks;

    public CreateMemberPresenter(ICreateMemberView view) 
        : this(view, new StubMemberTasks())
    {
    }

    public CreateMemberPresenter(ICreateMemberView view, IMemberTasks tasks)
    {
        this.view = view;
        this.tasks = tasks;

        HookupEventHandlersTo(view);
    }

    private void HookupEventHandlersTo(ICreateMemberView view)
    {
        view.CreateMember += delegate { CreateMember(); };
    }

    private void CreateMember()
    {
        if (!view.IsValid)
            return;

        try
        {
            int newUserId;
            tasks.CreateMember(view.NewMember, out newUserId);
            view.NewUserCode = newUserId;
            view.Notify(new NotificationDTO() { Type = NotificationType.Success });
        }
        catch(Exception e)
        {
            this.LogA().Message(string.Format("Error Creating User: {0}", e.Message));
            view.Notify(new NotificationDTO() { Type = NotificationType.Failure, Message = "There was an error creating a new member" });
        }
    }
}

Ich habe meine Hauptformularvalidierung mit den integrierten .Net-Validierungskontrollen durchgeführt, aber jetzt muss ich überprüfen, ob die Daten die Kriterien für die Serviceschicht ausreichend erfüllen.

Nehmen wir an, die folgenden Service-Layer-Meldungen können angezeigt werden:

  • E-Mail-Konto existiert bereits (Fehler)
  • Der eingegebene verweisende Benutzer existiert nicht (Fehler)
  • Die Länge des Passworts überschreitet die zulässige Länge des Datenspeichers (Fehler)
  • Mitglied erfolgreich erstellt (Erfolg)

Nehmen wir außerdem an, dass es in der Serviceschicht mehr Regeln geben wird, die die Benutzeroberfläche nicht vorhersehen kann.

Derzeit lasse ich die Serviceschicht eine Ausnahme auslösen, wenn die Dinge nicht wie geplant verlaufen.Ist das eine ausreichende Strategie?Riecht euch dieser Code?Wenn ich eine Serviceschicht wie diese schreiben würde, würden Sie sich darüber ärgern, Presenter schreiben zu müssen, die sie auf diese Weise verwenden?Rückkehrcodes scheinen zu altmodisch zu sein und ein Bool ist einfach nicht informativ genug.


Bearbeiten nicht von OP:Zusammenführen von Folgekommentaren, die vom OP als Antworten gepostet wurden


Cheekysoft, ich mag das Konzept einer ServiceLayerException.Ich habe bereits ein globales Ausnahmemodul für die Ausnahmen, die ich nicht erwarte.Finden Sie es mühsam, all diese benutzerdefinierten Ausnahmen festzulegen?Ich dachte, dass das Fangen der Basis-Exception-Klasse etwas stinkend war, war mir aber nicht ganz sicher, wie es von da an weitergeht.

tgmdbm, mir gefällt die clevere Verwendung des Lambda-Ausdrucks dort!


Vielen Dank an Cheekysoft für das Follow-up.Ich vermute also, dass dies die Strategie wäre, wenn es Ihnen nichts ausmacht, wenn dem Benutzer eine separate Seite angezeigt wird (ich bin in erster Linie ein Webentwickler), wenn die Ausnahme nicht behandelt wird.

Wenn ich die Fehlermeldung jedoch in derselben Ansicht zurückgeben möchte, in der der Benutzer die Daten übermittelt hat, die den Fehler verursacht haben, müsste ich dann die Ausnahme im Presenter abfangen?

So sieht CreateUserView aus, wenn der Presenter die ServiceLayerException behandelt hat:

Create a user

Für diese Art von Fehler ist es sinnvoll, ihn derselben Ansicht zu melden.

Wie auch immer, ich denke, dass wir jetzt den Rahmen meiner ursprünglichen Frage sprengen.Ich werde mit dem, was Sie gepostet haben, herumspielen und wenn ich weitere Details benötige, werde ich eine neue Frage posten.

War es hilfreich?

Lösung

Das klingt für mich genau richtig.Ausnahmen sind vorzuziehen, da sie von überall innerhalb der Serviceschicht an die Spitze der Serviceschicht geworfen werden können, unabhängig davon, wie tief sie in der Implementierung der Servicemethode verschachtelt sind.Dadurch bleibt der Servicecode sauber, da Sie wissen, dass der anrufende Moderator immer über das Problem informiert wird.

Ausnahme nicht abfangen

Jedoch, Ausnahme nicht abfangen Ich weiß, dass es im Presenter verlockend ist, weil es den Code kürzer hält, aber Sie müssen bestimmte Ausnahmen abfangen, um zu vermeiden, dass Ausnahmen auf Systemebene abgefangen werden.

Planen Sie eine einfache Ausnahmehierarchie

Wenn Sie Ausnahmen auf diese Weise verwenden möchten, sollten Sie eine Ausnahmehierarchie für Ihre eigenen Ausnahmeklassen entwerfen.Erstellen Sie mindestens eine ServiceLayerException-Klasse und werfen Sie eine davon in Ihre Servicemethoden, wenn ein Problem auftritt.Wenn Sie dann eine Ausnahme auslösen müssen, die vom Präsentator anders behandelt werden sollte/könnte, können Sie eine bestimmte Unterklasse von ServiceLayerException auslösen:sagen wir, AccountAlreadyExistsException.

Ihr Moderator hat dann die Möglichkeit dazu

try {
  // call service etc.
  // handle success to view
} 
catch (AccountAlreadyExistsException) {
  // set the message and some other unique data in the view
}
catch (ServiceLayerException) {
  // set the message in the view
}
// system exceptions, and unrecoverable exceptions are allowed to bubble 
// up the call stack so a general error can be shown to the user, rather 
// than showing the form again.

Durch die Verwendung der Vererbung in Ihren eigenen Ausnahmeklassen müssen Sie keine Multipile-Ausnahmen in Ihrem Presenter abfangen – das ist bei Bedarf möglich – und Sie fangen nicht versehentlich Ausnahmen ab, die Sie nicht verarbeiten können.Wenn sich Ihr Präsentator bereits ganz oben in der Aufrufliste befindet, fügen Sie einen Catch( Exception )-Block hinzu, um die Systemfehler mit einer anderen Ansicht zu behandeln.

Ich versuche immer, mir meine Serviceschicht als eine separate verteilbare Bibliothek vorzustellen und eine so spezifische Ausnahme auszulösen, wie es sinnvoll ist.Es ist dann Sache der Präsentator-/Controller-/Remote-Service-Implementierung, zu entscheiden, ob sie sich um die spezifischen Details kümmern muss oder Probleme einfach als allgemeinen Fehler behandeln muss.

Andere Tipps

Wie Cheekysoft vorschlägt, würde ich dazu neigen, alle wichtigen Ausnahmen in einen ExceptionHandler zu verschieben und diese Ausnahmen aufsprudeln zu lassen.Der ExceptionHandler würde die entsprechende Ansicht für den Ausnahmetyp rendern.

Alle Validierungsausnahmen sollten jedoch in der Ansicht behandelt werden, aber normalerweise ist diese Logik in vielen Teilen Ihrer Anwendung gleich.Deshalb möchte ich so einen Helfer haben

public static class Try {
    public static List<string> This( Action action ) {
      var errors = new List<string>();
      try {
        action();
      }
      catch ( SpecificException e ) {
        errors.Add( "Something went 'orribly wrong" );
      }
      catch ( ... )
      // ...
     return errors;
    }
}

Wenn Sie dann Ihren Dienst anrufen, gehen Sie einfach wie folgt vor

var errors = Try.This( () => {
  // call your service here
  tasks.CreateMember( ... );
} );

Dann ist „Fehler“ leer und Sie können loslegen.

Sie können dies weiterführen und es mit benutzerdefinierten Ausnahmehandlern erweitern, die Folgendes verarbeiten ungewöhnlich Ausnahmen.

Als Antwort auf die Folgefrage:

Da es mühsam wird, Ausnahmen zu erstellen, gewöhnt man sich irgendwie daran.Durch die Verwendung eines guten Codegenerators oder einer guten Vorlage kann die Ausnahmeklasse mit minimaler manueller Bearbeitung innerhalb von etwa 5 bis 10 Sekunden erstellt werden.

In vielen realen Anwendungen kann die Fehlerbehandlung jedoch 70 % der Arbeit ausmachen, sodass alles eigentlich nur ein Teil des Spiels ist.

Wie tgmdbm vorschlägt, lasse ich in MVC/MVP-Anwendungen alle meine nicht behandelbaren Ausnahmen nach oben sprudeln und werde vom Dispatcher abgefangen, der an einen ExceptionHandler delegiert.Ich habe es so eingerichtet, dass es einen ExceptionResolver verwendet, der in der Konfigurationsdatei nach einer geeigneten Ansicht sucht, die dem Benutzer angezeigt wird.Die Spring MVC-Bibliothek von Java macht dies sehr gut.Hier ist ein Ausschnitt aus einer Konfigurationsdatei für den Ausnahmelöser von Spring MVC – er ist für Java/Spring, aber Sie werden die Idee verstehen.

Dies nimmt Ihren Präsentatoren/Controllern insgesamt einen enormen Aufwand für die Ausnahmebehandlung ab.

<bean id="exceptionResolver"
      class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

  <property name="exceptionMappings">
    <props>
      <prop key="UserNotFoundException">
        rescues/UserNotFound
      </prop>
      <prop key="HibernateJdbcException">
        rescues/databaseProblem
      </prop>
      <prop key="java.net.ConnectException">
        rescues/networkTimeout
      </prop>
      <prop key="ValidationException">
        rescues/validationError
      </prop>
      <prop key="EnvironmentNotConfiguredException">
        rescues/environmentNotConfigured
      </prop>
      <prop key="MessageRejectedPleaseRetryException">
        rescues/messageRejected
      </prop>
    </props>
  </property>
  <property name="defaultErrorView" value="rescues/general" />
</bean>
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top