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

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

  • Загрузка объекта на основе данных маршрута (идентификаторов и т.п.)
    • Если данные маршрута не указывают на действительный объект (например, через хакерство URL), сообщите об этом пользователю и верните HTTP 404 Not Found
  • Подтвердите, что текущий пользователь имеет соответствующие разрешения для объекта
    • Если пользователь не имеет разрешения, сообщите об этом пользователю и верните запрещенный протокол HTTP 403
  • Если вышеописанное выполнено успешно, то сделайте что-нибудь с этим объектом, специфичным для конкретного действия (т.е. сделайте его в представлении).

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

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

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 извне определенного контроллера?

Ответ 1

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

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

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

Ответ 2

Просто прочитайте этот пост, поскольку у меня была такая же проблема из фильтра действий. Мое решение создало действие вида явно. Это основано на защищенном методе 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;
}

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

Ответ 3

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

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

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);. Могут быть много вариаций этого, основная идея - использовать обратный вызов.

Ответ 4

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

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

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