在ASP.NET MVC中的控制器之外创建ViewResults

时间:2010-02-22 19:26:43

标签: asp.net-mvc controller

我的一些控制器操作具有一组标准的故障处理行为。一般来说,我想:

  • 根据路线数据(ID等)加载对象
    • 如果路由数据未指向有效对象(例如:通过URL黑客攻击),则通知用户该问题并返回HTTP 404 Not Found
  • 验证当前用户是否具有该对象的适当权限
    • 如果用户没有权限,请通知用户该问题并返回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的最佳方法是什么?

4 个答案:

答案 0 :(得分:6)

刚看完这篇文章,因为我从动作过滤器中遇到了同样的问题。我的解决方案是明确地创建视图操作。这基于受MVC源的受保护View()方法,因此它应填充所需的属性。无论如何,似乎没有问题。

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

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

当天晚些时候,但这对我有用。

答案 1 :(得分:4)

当我写这篇文章时,我想到了一种方式。

我可以把它放在Controller的子类中,然后将这个类子类化为我的实际控制器,而不是将上面的代码放在帮助器中。这将允许我调用受保护的View()方法。

我不喜欢这个,因为它requires inheritance to work,但它仍然是一个选项。

答案 2 :(得分:2)

我有同样的问题并以不同的方式回答。我真的不想为此使用继承,所以我改用了lambda。

首先,我有一个对象,我从控制器传递给我想要返回视图的方法:

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);。这可能有很多变化,基本思路是使用回调。

答案 3 :(得分:0)

另一种方法是使用装饰器:

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

在您的控制器中。