开发者

Creating ViewResults outside of Controllers in ASP.NET MVC

开发者 https://www.devze.com 2022-12-21 09:17 出处:网络
Several of my controller actions have a standard set of failure-handling behavior. In general, I want to:

Several of my controller actions have a standard set of failure-handling behavior. In general, I want to:

  • 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 Forbi开发者_运维技巧dden
  • If the above is successful, then do something with that object that's action-specific (ie: render it in a view).

These steps are so standardized that I want to have reusable code to implement the behavior.

My current plan of attack was to have a helper method to do something like this:

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

The problem here is that Controller.View() is a protected method and so is not accessible from a helper. I've looked at creating a new ViewResult instance explicitly, but there's enough properties to set that I'm wary about doing so without knowing the pitfalls first.

What's the best way to create a ViewResult from outside a particular Controller?


Just read this post as I was having the same issue from an action filter. My solution was creating the view action explicitly. This is based on the protected View() method as per the MVC source so it should populate the required properties. Anyway, seems to work without issues.

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

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

A bit late in the day but this worked for me.


As I was writing this I thought of one way.

Rather than have the above code in a helper, I could put it into a subclass of Controller and then subclass this class for my actual controllers. This would allow me to call the protected View() method.

I don't like this particularly much because it requires inheritance to work, but it's still an option.


I had the same question and answered it differently. I really did not want to use inheritance for this, so I used a lambda instead.

First, I have an object that I pass from my controller to the method I want to return the view:

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

I create an instance of this and pass it as the parameter to the method that will return the result:

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

Then in the method I invoked, i can call context.View("ViewName", model);. There can be many variations of this, the basic idea is to use the callback.


Another way would be using decorators:

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

And maybe an extension method to make it cleaner:

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

You could then simply say:

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

in your controller.

0

精彩评论

暂无评论...
验证码 换一张
取 消