Создание ViewResults вне контроллеров в ASP.NET MVC

Некоторые из моих действий контроллера имеют стандартный набор поведения при обработке сбоев. В общем, я хочу:

  • Load an object based on the Route Data (IDs and the like)
    • If the Route Data does not point to a valid object (ex: through URL hacking) then inform the user of the problem and return an HTTP 404 Not Found
  • Validate that the current user has the proper permissions on the object
    • If the user doesn't have permission, inform the user of the problem and return an HTTP 403 Forbidden
  • Если вышеприведенное выполнено успешно, то сделайте что-нибудь с этим объектом, зависящим от действия (например, визуализируйте его в представлении).

Эти шаги настолько стандартизированы, что я хочу иметь многоразовый код для реализации поведения.

Мой текущий план атаки состоял в том, чтобы иметь вспомогательный метод, чтобы сделать что-то вроде этого:

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

Проблема здесь в том, что Controller.View() является защищенным методом и поэтому недоступен из помощника. Я рассматривал создание нового экземпляра ViewResult явно, но есть достаточно свойств, которые нужно установить, и я опасаюсь делать это, не зная сначала о подводных камнях.

Каков наилучший способ создать ViewResult вне определенного контроллера?


person Craig Walker    schedule 22.02.2010    source источник


Ответы (4)


Просто прочитайте этот пост, так как у меня была такая же проблема с фильтром действий. Мое решение заключалось в явном создании действия просмотра. Это основано на методе protected View() в соответствии с источником MVC, поэтому он должен заполнять необходимые свойства. Во всяком случае, вроде работает без проблем.

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

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

Немного поздно, но это сработало для меня.

person detaylor    schedule 13.12.2010

Когда я писал это, я подумал об одном способе.

Вместо того, чтобы иметь приведенный выше код в помощнике, я мог бы поместить его в подкласс контроллера, а затем подкласс этого класса для моих реальных контроллеров. Это позволило бы мне вызвать защищенный метод View().

Мне это особенно не нравится, потому что для работы требуется наследование, но это еще вариант.

person Craig Walker    schedule 22.02.2010
comment
Что мне не нравится в этом, так это то, что это означает, что контроллер может иметь только один набор общих действий: тот, который он (отдельно) наследует. Для этого конкретного примера это не проблема, но я вижу, что по мере роста проекта он становится все более громоздким. - person Craig Walker; 22.02.2010
comment
Я понимаю, что вашему вопросу много лет, но я просто подумал, что упомяну еще одну возможность. Вы можете открыть общедоступную версию View() под названием GetView() на своем базовом контроллере, тогда вы можете оставить своего помощника в отдельном классе. Вам все равно нужно будет передать свой контроллер вашему помощнику, что немного неудобно, но позволяет композицию вместо наследования. - person devuxer; 09.09.2012

У меня был тот же вопрос, и я ответил на него по-другому. Я действительно не хотел использовать для этого наследование, поэтому вместо этого я использовал лямбду.

Во-первых, у меня есть объект, который я передаю из моего контроллера в метод, который я хочу вернуть:

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

Я создаю экземпляр этого и передаю его в качестве параметра методу, который вернет результат:

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

Затем в вызванном мной методе я могу вызвать context.View("ViewName", model);. Может быть много вариантов этого, основная идея заключается в использовании обратного вызова.

person jhorback    schedule 17.07.2010

Другой способ - использовать декораторы:

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

И, возможно, метод расширения, чтобы сделать его чище:

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

Тогда вы могли бы просто сказать:

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

в вашем контроллере.

person kaa    schedule 18.05.2010
comment
Это не очень помогает, потому что вам все равно нужно создать базовый ViewResult в контроллере, от чего я пытаюсь уйти. - person Craig Walker; 18.05.2010