2008-08-22 4 views
16

Ich schreibe gerade eine ASP.Net App von der Benutzeroberfläche aus. Ich implementiere eine MVP-Architektur, weil ich Winforms satt habe und etwas wollte, das eine bessere Trennung von Bedenken bietet.Wie kommuniziere ich Service Layer Meldungen/Fehler zu höheren Schichten mit MVP?

Mit MVP behandelt der Presenter Ereignisse, die von der Ansicht ausgelöst werden. Hier einige Code, den ich an der richtigen Stelle mit der Schaffung von Benutzern zu beschäftigen:

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 meine Haupt-Formularvalidierung haben getan mit dem eingebauten in .Net Validation Controls, aber jetzt muss ich überprüfen, ob die Daten ausreichend erfüllt die Kriterien für die Serviceebene.

Sagen wir folgende Service Layer-Meldungen auftauchen können:

  • E-Mail-Konto bereits vorhanden ist (Fehler)
  • verweis Benutzer existiert nicht eingegeben (Fehler)
  • Kennwortlänge überschreitet Datenspeicher erlaubt Länge (Fehler)
  • Mitglied erfolgreich erstellt (Erfolg)

Lassen Sie uns auch sagen, dass m Orga-Regeln befinden sich in der Service-Schicht, die die Benutzeroberfläche nicht vorhersehen kann.

Derzeit habe ich die Service-Schicht eine Ausnahme werfen, wenn die Dinge nicht wie geplant ablaufen. Ist das eine ausreichende Strategie? Riecht dieser Code für euch? Wenn ich eine solche Service-Schicht schreiben würde, wäre es ärgerlich, Presenter schreiben zu müssen, die sie auf diese Weise benutzen? Rückkehrcodes scheinen zu alt zu sein und ein Bool ist einfach nicht informativ genug.


Bearbeiten nicht durch OP: Zusammenführung in nachfolgende Kommentare, die als Antworten des OP gebucht wurden


Cheekysoft, ich mag das Konzept eines ServiceLayerException. Ich habe bereits ein globales Ausnahmemodul für die Ausnahmen, die ich nicht erwarte. Finden Sie all diese benutzerdefinierten Ausnahmen mühsam? Ich dachte, dass das Auffangen der Basis-Exception-Klasse ein bisschen stinkend war, aber ich wusste nicht genau, wie es weiterging.

tgmdbm, Ich mag die clevere Verwendung der Lambda-Ausdruck dort!


Danke Cheekysoft für das Follow-up. Ich nehme an, das wäre die Strategie, wenn es Ihnen nichts ausmacht, wenn der Benutzer eine separate Seite anzeigt (ich bin hauptsächlich ein Webentwickler), wenn die Ausnahme nicht behandelt wird.

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

Hier ist, was die CreateUserView aussieht, wenn der Moderator die ServiceLayerException behandelt hat:

Create a user

Für diese Art von Fehler, es ist schön, es zu der gleichen Ansicht zu melden.

Wie auch immer, ich denke, wir gehen jetzt über den Rahmen meiner ursprünglichen Frage hinaus. Ich spiele mit dem, was du gepostet hast, und wenn ich weitere Details benötige, werde ich eine neue Frage stellen.

Antwort

15

Das klingt genau richtig für mich. Ausnahmen sind vorzuziehen, da sie von überall innerhalb der Service-Schicht an die oberste Ebene der Service-Schicht geworfen werden können, unabhängig davon, wie tief sie in der Service-Methoden-Implementierung verschachtelt sind. Dies hält den Dienstcode sauber, da Sie wissen, dass der aufrufende Moderator immer eine Benachrichtigung über das Problem erhält.

nicht Exception

Jedoch fangen Sie, don't catch Exception in dem Moderator, ich weiß seine verlockend, weil es den Code kürzer hält, aber Sie müssen bestimmte Ausnahmen fangen, die auf Systemebene Ausnahmen einklemmen.

planen Einfache Ausnahmehierarchie

Wenn Sie Ausnahmen auf diese Weise nutzen wollen, sollten Sie eine Ausnahme Hierarchie für die eigene Ausnahmeklassen entwerfen. Erstellen Sie zu einem Minimum eine ServiceLayerException-Klasse, und werfen Sie eine dieser Klassen 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: say, AccountAlreadyExistsException.

Ihr Moderator hat dann die Möglichkeit,

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. 

Vererbung in Ihren eigenen Ausnahmeklassen verwenden zu tun bedeutet, dass Sie nicht erforderlich sind multipile Ausnahmen in Ihrem Moderator zu fangen - Sie können, wenn es eine Notwendigkeit - und Sie fällen nicht versehentlich Ausnahmen, die Sie nicht verarbeiten können. Wenn sich Ihr Moderator bereits an der Spitze des Aufrufstapels befindet, fügen Sie einen Catch (Exception) -Block hinzu, um die Systemfehler mit einer anderen Ansicht zu behandeln.

Ich versuche immer und denke an meine Service-Schicht als separate verteilbare Bibliothek, und werfen Sie als spezifische eine Ausnahme als sinnvoll. Es liegt dann an der Implementierung von presenter/controller/remote-service, zu entscheiden, ob sie sich um die spezifischen Details kümmern oder nur Probleme als generischen Fehler behandeln muss.

+1

Was für eine nette Antwort :) – Mik378

3

Wie Cheekysoft vorschlägt, würde ich dazu neigen, alle wichtigen Ausnahmen in einen ExceptionHandler zu verschieben und diese Ausnahmen aufblühen zu lassen. Der ExceptionHandler würde die entsprechende Ansicht für den Typ der Ausnahme darstellen.

Alle Überprüfungsausnahmen sollten jedoch in der Ansicht behandelt werden, aber normalerweise ist diese Logik für viele Teile Ihrer Anwendung üblich. So möchte ich einen Helfer haben, wie diese

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

Dann, wenn Sie da gerade telefonieren Sie wie folgt vor

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

Dann in Fehler ist leer, du bist gut zu gehen.

Sie können dies weiter und erweitern Sie es mit custome Ausnahmehandlern, die unübliche Ausnahmen behandeln.

1

In Antwort auf die Folgefrage:

Wie Ausnahmen immer mühsam zu schaffen, erhalten Sie ein bisschen daran gewöhnt. Die Verwendung eines guten Codegenerators oder einer Vorlage kann die Ausnahmeklasse mit minimaler Handbearbeitung innerhalb von 5 oder 10 Sekunden erstellen.

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

Wie tgmdbm suggeriert, lasse ich in MVC/MVP-Anwendungen alle meine nicht handhabbaren Ausnahmen an die Spitze springen 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, um den Benutzer anzuzeigen. Die Spring MVC-Bibliothek von Java macht das sehr gut. Hier ist ein Ausschnitt aus einer Konfigurationsdatei für Spring MVC Exception Resolver - es ist für Java/Spring, aber Sie werden die Idee bekommen.

Dies erfordert eine große Menge an Exception Handling von Ihren Presenter/Controller insgesamt.

<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> 
Verwandte Themen