Extending Controllers

Controller actions are the glue that pulls together your application; they talk to models via data access layers, make rudimentary decisions about how to achieve activities on behalf of the user, and decide how to respond (with views, JSON, XML, and so on). Customizing how actions are selected and executed is an important part of the MVC extensibility story.

Selecting Actions

ASP.NET MVC enables influencing how actions are selected for execution through two mechanisms: choosing action names and selecting (filtering) action methods.

Choosing Action Names with Name Selectors

Renaming an action is handled by attributes that derive from ActionNameSelectorAttribute. The most common use of action name selection is through the [ActionName] attribute that ships with the MVC framework. This attributes allows the user to specify an alternative name and attach it directly to the action method itself. Developers who need a more dynamic name mapping can implement their own custom attribute derived from ActionNameSelectorAttribute.

Implementing ActionNameSelectorAttribute is a simple task: implement the IsValidName abstract method, and return true or false as to whether the requested name is valid. Because the action name selector is allowed to vote on whether or not a name is valid, the decision can be delayed until you know what name the request is asking for.

For example, say you wanted to have a single action that handled any request for an action name that began with “product-” (perhaps you need to map some existing URL that you cannot control). By implementing a custom naming selector, you can do that quite easily:

public override bool IsValidName(ControllerContext controllerContext,
                                 string actionName,
                                 MethodInfo methodInfo) {
    return actionName.StartsWith("product-");
}

When you apply this new attribute to an action method, it responds to any action that begins with “product-”. The action stills need to do more parsing of the actual action name to extract the extra information. You can see an example of this in the code in ∼/Areas/ActionNameSelector. The sample includes parsing of the product ID out from the action name, and placing that value into the route data so that the developer can then model bind against the value.

Filtering Actions with Method Selectors

The other action selection extensibility point is filtering actions. A method selector is an attribute class that derives from ActionMethodSelectorAttribute. Much like action name selection, this involves a single abstract method that is responsible for inspecting the controller context and method, and saying whether the method is eligible for the request. There are several built-in implementations of this attribute in the MVC framework: [AcceptVerbs] (and its closely related attributes [HttpGet], [HttpPost], [HttpPut], and [HttpDelete]) as well as [NonAction].

If a method selector returns false when MVC calls its IsValidForRequest method, the method is not considered valid for the given request and the system keeps looking for a match. If no matching method is found, the system returns an HTTP 404 error code in response to the request. Similarly; if more than one method matches a request, the system returns an HTTP 500 error code (and tells you about the ambiguity on the error page).

If you're wondering why [Authorize] isn't in the preceding list, it's because the correct action for [Authorize] is to either allow the request or to return an HTTP 401 (“Unauthorized”) error code, so that the browser knows that you need to authenticate. Another way to think of it is that, for [AcceptVerbs] or [NonAction], there is nothing the end user can do to make the request valid; it's always going to be invalid (because it is using the wrong HTTP verb, or trying to call a non-action method), whereas [Authorize] implies that the end user could do something to eventually make the request succeed. That's the key difference between an action filter like [Authorize] and a method selector like [AcceptVerbs].

An example of a place where you might use a custom method selector is to differentiate Ajax requests from non-Ajax requests. You could implement a new [AjaxOnly] action method selector with the IsValidForRequest method as follows:

public override bool IsValidForRequest(ControllerContext controllerContext,
                                       MethodInfo methodInfo) {
    return controllerContext.HttpContext.Request.IsAjaxRequest();
}

With an attribute like this available, you can then create separate action methods that have the same name, but are dispatched based on whether the user appears to be making a direct request in a browser versus a programmatic Ajax request. You may choose to do different work based on whether the user is making a full request or an Ajax request. You can find a full example of this in ∼/Areas/ActionMethodSelector. It contains the implementation of the [AjaxOnly] attribute, as well the controller and view that show the system choosing between two Index methods, depending on whether the user is making a full request or an Ajax request.

Action Filters

Once an action method has been selected, the action is then executed, and if it returned a result, the result is then executed. Action filters allow the developer to participate in the action and result execution pipeline in four ways: for authorization, for pre- and post-processing of actions, for pre- and post-processing of results, and for error handling.

Action filters can be written as attributes that are applied directly to the action methods (or controller classes), or as standalone classes that are registered in the global filter list. If you intend to use your action filter as an attribute, it must derive from FilterAttribute (or any subclass, such as ActionFilterAttribute). A global action filter that is not an attribute has no base class requirements. Regardless of which route you take, the filtering activities you support are determined by the interfaces you implement.

Authorization Filters

An action filter that wants to participate in authorization implements the IAuthorizationFilter interface. Authorization filters execute very early in the action pipeline, so they're appropriately used for activities that short circuit the entire action execution. Several classes in the MVC framework implement this interface, including [Authorize], [ChildActionOnly], [RequireHttps], [ValidateAntiForgeryToken], and [ValidateInput].

A developer might choose to implement an authorization filter to provide this kind of early escape from the action pipeline when some pre-condition isn't properly met and where the resulting behavior is something other than returning an HTTP 404 error code.

Action and Result Filters

An action filter that wants to participate in pre- and post-processing of actions should implement the IActionFilter interface. This interface offers two methods to implement: OnActionExecuting (for pre-processing) and OnActionExecuted (for post-processing). Similarly, for pre- and post-processing of results, an action filter should implement IResultFilter, with its two filter methods: OnResultExecuting and OnResultExecuted. There are two action/result filters in the MVC framework itself: [AsyncTimeout] and [OutputCache]. A single action filter often implements both of these interfaces as a pair, so it makes sense to talk about them together.

The output cache filter is an excellent example of this pairing of action and result filter. It overrides OnActionExecuting to determine whether it already has a cached answer (and can thereby completely bypass the action and result execution, and instead return a result directly from its cache). It also overrides OnResultExecuted so that it can save away the results of executing an as-yet un-cached action and result.

For an example of this, look at the code in the sample at ∼/Areas/TimingFilter. This is an action and result filter that records the amount of time that the action and result takes to execute. The four overridden methods look like this:

public void OnActionExecuting(ActionExecutingContext filterContext)
{
    GetStopwatch("action").Start();
}

public void OnActionExecuted(ActionExecutedContext filterContext)
{
    GetStopwatch("action").Stop();
}

public void OnResultExecuting(ResultExecutingContext filterContext)
{
    GetStopwatch("result").Start();
}

public void OnResultExecuted(ResultExecutedContext filterContext)
{
    var resultStopwatch = GetStopwatch("result");
    resultStopwatch.Stop();

    var actionStopwatch = GetStopwatch("action");
    var response = filterContext.HttpContext.Response;

    if (!filterContext.IsChildAction && response.ContentType == "text/html")
        response.Write(
            String.Format(
                "<h5>Action ‘{0} :: {1}’, Execute: {2}ms, Result: {3}ms.</h5>",
                filterContext.RouteData.Values["controller"],
                filterContext.RouteData.Values["action"],
                actionStopwatch.ElapsedMilliseconds,
                resultStopwatch.ElapsedMilliseconds
            )
        );
}

The example keeps two instances of the .NET Stopwatch class, one for action execution and one for result execution, and when it's done, it appends some HTML to the output stream so that you can see exactly how much time was spent running the code.

Exception Filters

The final kind of action filter available is the exception filter, used to process exceptions that might be thrown during action or result execution. An action filter that wants to participate in the handling of exceptions should implement the IExceptionFilter interface. In the MVC framework, there is a single exception filter: [HandleError].

Developers often use exception filters to perform some sort of logging of the errors, notification of the system administrators, and choosing how to handle the error from the end user's perspective (usually by sending the user to an error page). The HandleErrorAttribute class does this last operation, so it's quite common to create an exception filter attribute by deriving from HandleErrorAttribute, and then overriding the OnException method to provide additional handling before calling base.OnException.

Providing Custom Results

The final line of code in most action methods returns an action result object. For example, the View method on the Controller class returns an instance of ViewResult, which contains the code necessary to look up a view, execute it, and write its results out to the response stream. When you write return View(); in your action, you're asking the MVC framework to execute a view result on your behalf.

As a developer, you're not limited to the action results provided by the MVC framework. You can make your own action result by deriving it from the ActionResult class and implementing ExecuteResult.

Why Have Action Results?

You may be asking yourself why MVC bothers to have action results. Couldn't the Controller class just have been built with the knowledge of how to render views, and have its View method just do the right thing?

The previous two chapters covered somewhat related topics: dependency injection and unit testing. Both those chapters talked about the importance of good software design. In this case, action results are serving two very important purposes.

  • The Controller class is a convenience, but is not a core part of the MVC framework. From the MVC run time's perspective, the important type is IController; to be (or consume) a controller in MVC, that's the only thing you need to understand. So clearly, putting view-rendering logic inside the Controller class would have made it much more difficult to re-use this logic elsewhere. Besides, should a controller really be forced to know how to render a view, when that is not its job? The principle at play here is the Single Responsibility Principle. The controller should be focused only on actions necessary for being a controller.
  • We wanted to enable good unit testing throughout the framework. By using action result classes, we enable developers to write simple unit tests that directly call action methods, and inspect the action result return values that result. It is much simpler to unit test an action result's parameters than it is to pick through the HTML that might be generated by rendering a view.

In the example in ∼/Areas/CustomActionResult you have an XML action result class that serializes an object into an XML representation and sends it down to the client as a response. In the full sample code, you have a custom Person class that is serialized from within the controller:

public ActionResult Index() {
    var model = new Person {
        FirstName = "Brad",
        LastName = "Wilson",
        Blog = "http://bradwilson.typepad.com"
    };

    return new XmlResult(model);
}

The implementation of the XmlResult class relies upon the built-in XML serialization capabilities of the .NET Framework:

public class XmlResult : ActionResult {
    private object data;

    public XmlResult(object data) {
        this.data = data;
    }

    public override void ExecuteResult(ControllerContext context) {
        var serializer = new XmlSerializer(data.GetType());
        var response = context.HttpContext.Response.OutputStream;

        context.HttpContext.Response.ContentType = "text/xml";
        serializer.Serialize(response, data);
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.79.176