views:

345

answers:

3

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 Forbidden
  • 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?

+1  A: 

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.

Craig Walker
+1: That's how I would do it.
Robert Harvey
The thing I don't like about this is that it means that a controller can only have one set of shared action behavior: that which it (singly) inherits. For this particular example it's not an issue, but I can see it getting much more unwieldy as the project grows.
Craig Walker
A: 

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.

kaa
This doesn't really help, because you still have to create the base ViewResult in the controller, which is what I'm trying to get away from.
Craig Walker
A: 

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 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.

jhorback