Domanda

Attualmente sto scrivendo un'app ASP.Net dall'interfaccia utente in basso.Sto implementando un'architettura MVP perché sono stufo di Winforms e volevo qualcosa che avesse una migliore separazione delle preoccupazioni.

Quindi con MVP, il Presenter gestisce gli eventi generati dalla View.Ecco del codice che ho in atto per gestire la creazione degli utenti:

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

Ho eseguito la convalida del modulo principale utilizzando i controlli di convalida .Net integrati, ma ora devo verificare che i dati soddisfino sufficientemente i criteri per il livello di servizio.

Supponiamo che possano essere visualizzati i seguenti messaggi del livello di servizio:

  • L'account di posta elettronica esiste già (errore)
  • L'utente referente inserito non esiste (errore)
  • La lunghezza della password supera la lunghezza consentita dall'archivio dati (errore)
  • Membro creato con successo (successo)

Diciamo anche che nel livello di servizio saranno presenti più regole che l'interfaccia utente non può prevedere.

Attualmente il livello di servizio genera un'eccezione se le cose non sono andate come previsto.È una strategia sufficiente?Questo codice vi puzza, ragazzi?Se scrivessi un livello di servizio come questo ti darebbe fastidio dover scrivere Presenter che lo utilizzano in questo modo?I codici di ritorno sembrano troppo vecchi e un bool non è abbastanza informativo.


Modifica non da OP:fondendosi nei commenti di follow-up che sono stati pubblicati come risposte dall'OP


Cheekysoft, mi piace il concetto di ServiceLayerException.Ho già un modulo di eccezione globale per le eccezioni che non prevedo.Trovi noioso fare tutte queste eccezioni personalizzate?Stavo pensando che catturare la classe Exception base fosse un po' puzzolente, ma non ero esattamente sicuro di come sarebbero progrediti da lì.

tgmdbm, mi piace l'uso intelligente dell'espressione lambda lì!


Grazie Cheekysoft per il follow-up.Quindi immagino che questa sarebbe la strategia se non ti dispiace che all'utente venga visualizzata una pagina separata (sono principalmente uno sviluppatore web) se l'eccezione non viene gestita.

Tuttavia, se desidero restituire il messaggio di errore nella stessa visualizzazione in cui l'utente ha inviato i dati che hanno causato l'errore, dovrei rilevare l'eccezione nel Presenter?

Ecco come appare CreateUserView quando Presenter ha gestito ServiceLayerException:

Create a user

Per questo tipo di errore è bello segnalarlo alla stessa vista.

Ad ogni modo, penso che ora stiamo andando oltre lo scopo della mia domanda originale.Giocherò con ciò che hai pubblicato e se avrò bisogno di ulteriori dettagli pubblicherò una nuova domanda.

È stato utile?

Soluzione

Mi sembra giusto.Le eccezioni sono preferibili in quanto possono essere lanciate all'inizio del livello di servizio da qualsiasi punto all'interno del livello di servizio, indipendentemente da quanto profondamente annidate all'interno dell'implementazione del metodo di servizio.Ciò mantiene pulito il codice del servizio poiché sai che il relatore chiamante riceverà sempre una notifica del problema.

Non cogliere l'eccezione

Tuttavia, non cogliere l'eccezione nel presentatore, so che è allettante perché mantiene il codice più breve, ma è necessario rilevare eccezioni specifiche per evitare di rilevare eccezioni a livello di sistema.

Pianifica una semplice gerarchia di eccezioni

Se intendi utilizzare le eccezioni in questo modo, dovresti progettare una gerarchia di eccezioni per le tue classi di eccezioni.Crea almeno una classe ServiceLayerException e inserisci una di queste nei metodi di servizio quando si verifica un problema.Quindi, se devi lanciare un'eccezione che dovrebbe/potrebbe essere gestita diversamente dal presentatore, puoi lanciare una sottoclasse specifica di ServiceLayerException:diciamo, AccountAlreadyExistsException.

Il tuo presentatore avrà quindi la possibilità di farlo

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.

Usare l'ereditarietà nelle tue classi di eccezioni significa che non sei obbligato a catturare eccezioni multiple nel tuo presentatore - puoi farlo se ce n'è bisogno - e non finisci per catturare accidentalmente eccezioni che non puoi gestire.Se il tuo relatore è già in cima allo stack di chiamate, aggiungi un blocco catch( Exception ) per gestire gli errori di sistema con una visualizzazione diversa.

Cerco sempre di pensare al mio livello di servizio come a una libreria distribuibile separata e di lanciare un'eccezione tanto specifica quanto ha senso.Spetta quindi al presentatore/controllore/implementazione del servizio remoto decidere se deve preoccuparsi dei dettagli specifici o semplicemente trattare i problemi come un errore generico.

Altri suggerimenti

Come suggerisce Cheekysoft, tenderei a spostare tutte le eccezioni principali in un ExceptionHandler e lasciare che tali eccezioni emergano.L'ExceptionHandler restituirà la vista appropriata per il tipo di eccezione.

Eventuali eccezioni di convalida, tuttavia, dovrebbero essere gestite nella vista, ma in genere questa logica è comune a molte parti dell'applicazione.Quindi mi piace avere un aiutante come questo

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

Quindi, quando chiami il tuo servizio, procedi come segue

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

Quindi negli errori è vuoto, sei a posto.

Puoi andare oltre ed estenderlo con gestori di eccezioni personalizzati che gestiscono raro eccezioni.

In risposta alla domanda successiva:

Per quanto riguarda la creazione di eccezioni che diventano noiose, in un certo senso ti ci abitui.L'uso di un buon generatore di codice o modello può creare la classe di eccezione con una modifica manuale minima in circa 5 o 10 secondi.

Tuttavia, in molte applicazioni del mondo reale, la gestione degli errori può rappresentare il 70% del lavoro, quindi in realtà fa tutto parte del gioco.

Come suggerisce tgmdbm, nelle applicazioni MVC/MVP lascio che tutte le mie eccezioni non gestibili arrivino in cima e vengano catturate dal dispatcher che delega a un ExceptionHandler.L'ho configurato in modo che utilizzi un ExceptionResolver che cerca nel file di configurazione per scegliere una visualizzazione appropriata da mostrare all'utente.La libreria Spring MVC di Java lo fa molto bene.Ecco uno snippet da un file di configurazione per il risolutore di eccezioni di Spring MVC: è per Java/Spring ma avrai un'idea.

Ciò richiede del tutto un'enorme quantità di gestione delle eccezioni dai presentatori/controller.

<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>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top