在ASP.NET MVC中,如何在控制器之外创建ViewResults?

12

我的多个控制器操作有一套标准的故障处理行为。通常,我想要:

  • 基于路由数据(如ID等)加载对象
    • 如果路由数据未指向有效的对象(例如通过URL黑客),则通知用户该问题并返回HTTP 404未找到
  • 验证当前用户是否对该对象具有适当的权限
    • 如果用户没有权限,则告知用户该问题并返回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()是受保护的方法,因此无法从helper中访问。我尝试过显式创建一个新的ViewResult实例,但由于不知道可能出现的问题,对需要设置的属性有些谨慎。

在特定的Controller之外创建ViewResult的最佳方法是什么?

4个回答

7

我读到这篇帖子时遇到了同样的问题,来自一个操作筛选器。我的解决方法是明确创建视图操作。这是基于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;
}

这对我来说有点晚了,但这对我起作用了。

6

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

与其在助手中编写上述代码,我可以将其放入Controller的子类中,然后为我的实际控制器创建子类。这将允许我调用受保护的View()方法。

我并不是特别喜欢这种方式,因为它需要继承才能使用,但这仍然是一个选项。


我不喜欢的是,这意味着控制器只能有一个共享行为集:即它(单独)继承的那个。对于这个特定的例子来说,这不是问题,但我可以看到随着项目的增长,它会变得更加难以处理。 - Craig Walker
我知道你的问题已经几年了,但我想提及另一种可能性。你可以在基础控制器上公开一个名为GetView()View()公共版本,然后将你的帮助程序保留在单独的类中。你仍然需要将你的控制器传递到你的帮助程序中,这有点尴尬,但它确实允许组合而不是继承。 - devuxer

1

我曾经有同样的问题,但我的解决方法不同。我真的不想使用继承来解决这个问题,所以我使用了一个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);。这有许多变化,基本思想是使用回调函数。


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

在你的控制器中。


1
这并没有真正帮助,因为你仍然需要在控制器中创建基本的ViewResult,而这正是我想要避免的。 - Craig Walker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接