12 Stimmen

Erstellen von ViewResults außerhalb von Controllern in ASP.NET MVC

Mehrere meiner Controller-Aktionen haben einen Standardsatz von Fehlerbehandlungsverhalten. Im Allgemeinen möchte ich:

  • Laden eines Objekts auf der Grundlage der Routendaten (IDs und dergleichen)
    • Wenn die Routendaten nicht auf ein gültiges Objekt verweisen (z.B. durch URL-Hacking), dann informieren Sie den Benutzer über das Problem und geben eine HTTP 404 Not Found zurück.
  • Überprüfen, ob der aktuelle Benutzer die richtigen Berechtigungen für das Objekt hat
    • Wenn der Benutzer keine Berechtigung hat, informieren Sie den Benutzer über das Problem und geben Sie eine HTTP 403 Forbidden
  • Wenn die obigen Schritte erfolgreich sind, dann tun Sie etwas mit diesem Objekt, das aktionsspezifisch ist (d.h.: rendern Sie es in einer Ansicht).

Diese Schritte sind so standardisiert, dass ich wiederverwendbaren Code zur Implementierung des Verhaltens haben möchte.

Mein derzeitiger Plan war es, eine Hilfsmethode zu haben, um so etwas zu tun:

public static ActionResult HandleMyObject(this Controller controller, 
    Func<MyObject,ActionResult> onSuccess) {
  var myObject = MyObject.LoadFrom(controller.RouteData).
  if ( myObject == null ) return NotFound(controller);
  if ( myObject.IsNotAllowed(controller.User)) return NotAllowed(controller);
  return onSuccess(myObject);
}

# NotAllowed() is pretty much the same as this
public static NotFound(Controller controller){
    controller.HttpContext.Response.StatusCode = 404
    # NotFound.aspx is a shared view.
    ViewResult result = controller.View("NotFound");
    return result;
}

Das Problem hier ist, dass Controller.View() eine geschützte Methode ist und daher nicht von einem Helfer zugänglich ist. Ich habe mir überlegt, eine neue ViewResult-Instanz explizit zu erstellen, aber es gibt so viele Eigenschaften, dass ich vorsichtig bin, ohne die Fallstricke vorher zu kennen.

Was ist der beste Weg, um ein ViewResult von außerhalb eines bestimmten Controllers zu erstellen?

7voto

detaylor Punkte 6986

Ich habe gerade diesen Beitrag gelesen, da ich das gleiche Problem mit einem Aktionsfilter hatte. Meine Lösung war die Erstellung der Ansicht Aktion explizit. Dies basiert auf der geschützten View()-Methode gemäß der MVC-Quelle, so dass es die erforderlichen Eigenschaften auffüllen sollte. Wie auch immer, scheint ohne Probleme zu arbeiten.

public static NotFound(Controller controller){
    controller.HttpContext.Response.StatusCode = 404;

    ViewResult result = new ViewResult {
            ViewName = "NotFound",
            ViewData = controller.ViewData,
            TempData = controller.TempData
        };
    return result;
}

Etwas spät, aber bei mir hat es funktioniert.

6voto

Craig Walker Punkte 46757

Als ich dies schrieb, fiel mir eine Möglichkeit ein.

Anstatt den obigen Code in einer Hilfsklasse zu haben, könnte ich ihn in eine Unterklasse von Controller einfügen und dann diese Klasse für meine eigentlichen Controller unterordnen. Dies würde es mir ermöglichen, die geschützte Methode View() aufzurufen.

Das gefällt mir nicht besonders gut, weil es erfordert Vererbung, um zu funktionieren aber es ist immer noch eine Option.

1voto

jhorback Punkte 813

Ich hatte die gleiche Frage und habe sie anders beantwortet. Ich wollte wirklich nicht, Vererbung für diese zu verwenden, so dass ich stattdessen ein Lambda verwendet.

Zunächst habe ich ein Objekt, das ich von meinem Controller an die Methode übergebe, mit der ich die Ansicht zurückgeben möchte:

public struct MyControllerContext
{
    public HttpRequestBase Request { get; set; }
    public HttpResponseBase Response { get; set; }
    public DocsController Controller { get; set; }

    public Func<string, object, ViewResult> ViewResult;
    public ViewResult View(string viewName, object model)
    {
        return this.ViewResult(viewName, model);
    }
}

Ich erstelle eine Instanz davon und übergebe sie als Parameter an die Methode, die das Ergebnis zurückgeben soll:

// In the controller
var context = new DocsControllerContext()
{
    Request = Request,
    Response = Response,
    Controller = this,
    ViewResult = (viewName, model) =>
    {
        return View(viewName, model);
    }
};

var returnValue = methodInfo.Invoke(toInvoke, new object[] { context });
return returnValue;

Dann kann ich in der Methode, die ich aufgerufen habe, Folgendes aufrufen context.View("ViewName", model); . Es kann viele Variationen davon geben, die Grundidee ist, den Rückruf zu verwenden.

0voto

kaa Punkte 1441

Eine andere Möglichkeit wäre die Verwendung von Dekoratoren:

public class StatusCodeViewResultDecorator : ViewResult {
  ViewResult wrapped;
  int code;
  string description;

  public StatusCodeViewResultDecorator( ViewResult wrapped, int code, string description ) {
    this.wrapped = wrapped;
    this.code = code;
    this.description = description;
  }

  public override void ExecuteResult(ControllerContext context) {
    wrapped.ExecuteResult(context);
    context.RequestContext.HttpContext.Response.StatusCode = code;
    context.RequestContext.HttpContext.Response.StatusDescription = description;
  }
}

Und vielleicht eine Erweiterungsmethode, um es sauberer zu machen:

public static class ViewResultExtensions {
  public static ViewResult WithStatus( this ViewResult viewResult, int code, string description ) {
    return new StatusCodeViewResultDecorator(viewResult,code,description);
  }
}

Sie könnten dann einfach sagen:

return View("MyView").WithStatus(404,"Not found");

in Ihrem Controller.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X