Frage So verschieben Sie die Validierungsbehandlung von einer Controller-Aktion zu einem Decorator


Wartung Bearbeiten

Nachdem ich diesen Ansatz für eine Weile benutzt hatte, fand ich nur den exakt gleichen Code in jedem Controller, also entschied ich mich für Reflektionszauber. In der Zwischenzeit habe ich mit MVC für meine Ansichten gehackt - Razor ist nur so langweilig und hässlich - also benutze ich meine Handler grundsätzlich als JSON-Backend. Der Ansatz, den ich derzeit verwende, besteht darin, meine Abfragen / Befehle mit a zu dekorieren Route Attribut, das sich in einer bestimmten allgemeinen Assembly befindet:

[Route("items/add", RouteMethod.Post)]
public class AddItemCommand { public Guid Id { get; set; } }

[Route("items", RouteMethod.Get)]
public class GetItemsQuery : IQuery<GetItemsResponse> { }

// The response inherits from a base type that handles
// validation messages and the like
public class GetItemsResponse : ServiceResponse { }

Ich habe dann einen MVC-Host implementiert, der die annotierten Befehle / Abfragen extrahiert und die Controller und Handler für mich beim Start generiert. Damit ist meine Anwendungslogik endlich frei von MVC-Gruft. Die Abfrageantworten werden automatisch mit Validierungsnachrichten gefüllt. Meine MVC-Anwendungen sehen jetzt alle so aus:

+ MvcApp
  + - Global.asax
  + - Global.asax.cs - Starten Sie den Host und fertig
  + - Web.config

Nachdem ich realisiert habe, dass ich MVC nicht wirklich außerhalb des Hosts verwende - und ständig Probleme mit den bazillion Abhängigkeiten habe, die das Framework hat - habe ich einen anderen Host basierend auf implementiert NServiceKit. Nichts musste in meiner Anwendungslogik geändert werden, und die Abhängigkeiten bestehen darin System.Web, NServiceKit und NServiceKit.Text das passt gut auf die Modellbindung. Ich weiß, dass es eine sehr ähnliche Herangehensweise ist NServiceKit/ServiceStack tut ihre Sachen, aber ich bin jetzt völlig entkoppelt von dem Web-Framework im Einsatz, so dass für den Fall, dass eine bessere kommt ich nur einen anderen Host implementieren und das ist es.

Die Situation

Ich arbeite derzeit an einer ASP.NET MVC-Site, die die Trennung von Businesslogic View über die implementiert IQueryHandler und ICommandHandler Abstraktionen (unter Verwendung von der allmächtige SimpleInjector zur Abhängigkeitsinjektion).

Das Problem

Ich muss eine benutzerdefinierte Validierungslogik an ein QueryHandler über einen Dekorateur und das funktioniert ziemlich gut an und für sich. Das Problem ist, dass ich im Falle von Validierungsfehlern dieselbe Ansicht anzeigen möchte, dass die Aktion zurückgegeben wurde, aber natürlich mit Informationen zum Validierungsfehler. Hier ist ein Beispiel für meinen Fall:

public class HomeController : Controller
{
    private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;

    public ActionResult Index()
    {
        try
        {
            var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
            // Doing something awesome with the data ...
            return this.View(new HomeViewModel());
        }
        catch (ValidationException exception)
        {
            this.ModelState.AddModelErrors(exception);
            return this.View(new HomeViewModel());
        }
    }
}

In diesem Szenario habe ich eine Geschäftslogik, die von der queryHandler das ist mit einem verziert ValidationQueryHandlerDecorator das wirft ValidationExceptions wenn es angemessen ist.

Was ich will, dass es tut

Was ich will, ist etwas in der Art von:

public class HomeController : Controller
{
    private readonly IQueryHandler<SomeQuery, SomeTransport> queryHandler;

    public ActionResult Index()
    {
        var dto = this.queryHandler.Handle(new SomeQuery { /* ... */ });
        // Doing something awesome with the data ...
        // There is a catch-all in place for unexpected exceptions but
        // for ValidationExceptions I want to do essentially the same
        // view instantiation but with the model errors attached
        return this.View(new HomeViewModel());
    }
}

Ich habe über ein besonderes nachgedacht ValidationErrorHandlerAttribute aber dann verliere ich den Kontext und ich kann nicht wirklich die richtige Ansicht zurückgeben. Das gleiche gilt für den Ansatz, bei dem ich einfach den IQueryHandler<,> mit einem Dekorateur ... Ich habe einige seltsame Code-Teile gesehen, die auf der Route ein paar Schnipsel schnüffelten und dann einen neuen Controller und Viewmodel per Instanziierung einleiteten Activator.CreateInstance - Das scheint keine gute Idee zu sein.

Ich frage mich, ob es einen schönen Weg dafür gibt ... Vielleicht sehe ich einfach nicht das Holz von den Bäumen. Vielen Dank!


10
2018-06-03 08:33


Ursprung


Antworten:


Ich denke nicht, dass es eine Möglichkeit gibt, die Aktionsmethode dazu zu bringen, da die Aktionsmethode das zurückgegebene Ansichtsmodell kontrolliert und im Falle einer Gültigkeitsausnahme ein Ansichtsmodell mit allen tatsächlichen Daten zurückgegeben werden muss ( um zu verhindern, dass der Benutzer seine Änderungen verliert). Was Sie jedoch tun können, um dies bequemer zu machen, ist das Hinzufügen einer Erweiterungsmethode zum Ausführen von Abfragen in einer Aktion:

public ActionResult Index()
{
    var result = this.queryHandler.ValidatedHandle(this.ModelState, new SomeQuery { });

    if (result.IsValid) {
        return this.View(new HomeViewModel(result.Data));
    }
    else
    {
        return this.View(new HomeViewModel());
    }
}

Das ValidatedHandle Erweiterungsmethode könnte folgendermaßen aussehen:

public static ValidatedResult<TResult> ValidatedHandle<TQuery, TResult>(
    this IQueryHandler<TQuery, TResult> handler,
    TQuery query, ModelStateDictionary modelState)
{
    try
    {
        return new ValidatedResult<TResult>.CreateValid(handler.Handle(query));
    }
    catch (ValidationException ex)
    {
        modelState.AddModelErrors(ex);
        return ValidatedResult<TResult>.Invalid;
    }
}

Beachten Sie, dass Sie eine solche Validierungsausnahme nur abfangen sollten, wenn die Validierung auf Daten erfolgt, die der Benutzer eingegeben hat. Wenn Sie eine Abfrage mit programmgesteuerten Parametern senden, bedeutet eine Validierungsausnahme einfach einen Programmierfehler, und Sie sollten einen Blog erstellen, die Ausnahme protokollieren und dem Benutzer eine benutzerfreundliche Fehlerseite anzeigen.


2
2018-06-03 10:02