13 ASP.NET MVC Extensibility Points that we
require to know:
One of the main design principles ASP.NET MVC
has been designed with is extensibility. Everything (or most of) in the
processing pipeline is replaceable so, if you don't like the conventions (or
lack of them) that ASP.NET MVC uses, you can create your own services to support
your conventions and inject them into the main pipeline.
In this post I'm going to show 13
extensibility points that every ASP.NET MVC developer should know, starting from
the beginning of the pipeline and going forward till the rendering of the view.
1. RouteConstraint
Usually we could put some constrains on url
parameters using regular expressions, but if your constrains depend on something
that is not only about the single parameter, you can implement the
IRouteConstrainsmethod and put your validation logic in it.
One example of this is the validation of a date: imagine any url that has year,
month and date on different url tokens, and you want to be able to validate that
the three parts make a valid date.
2. RouteHandler
Not really specific to ASP.NET MVC, the RouteHandler is the component that
decide what to do after the route has been selected. Obviously if you change
the RouteHandler you end up handling the request without ASP.NET MVC, but this
can be useful if you want to handle a route directly with some specific
HttpHanlders or even with a classic WebForm.
3. ControllerFactory
The controller factory is the component that, based on the route, chooses which
controller to instantiate and instantiate it. The default factory looks for
anything that implements IController and whose name ends with Controller, and
than create an instance of it through reflection, using the parameter-less
constructor.
But if you want to use Dependency Injection you cannot use it, and you have to
use a IoC aware controller factory: there are already controller factory for
most of the IoC containers. You can find them in MvcContrib or having a look at
the Ninject Controller Factory.
4. ActionInvoker
ActionInvoker is responsible for invoking the action based on it's name. The
default action invoker looks for the action based on the method name, the action
name and possibly other selector attributes. Then it invokes the action method
together with any filter defined and finally it executes the action result.
If you read carefully you probably understood that most of the execution
pipeline is inside the logic of the default ControllerActionInvoker class. So if
you want to change any of these conventions, from the action method's selection
logic, to the way http parameters are mapped to action parameters, to the way
filters are chosen and executed, you have to extend that class and override the
method you want to change.
5. ActionMethodSelectorAttribute
Actions, with the default action invoker, are selected based on their name, but
you can finer tune the selection of actions implementing your own Method
Selector. The framework already comes with the AcceptVerbs attribute that allows
you to specify to which HTTP Verb an action has to respond to.
A possible scenario for a custom selector attribute is if you want to choose one
action or another based on languages supported by the browser or based on the
type of browser, for example whether it is a mobile browser or a desktop
browser.
6. AuthorizationFilter
These kind of filters are executed before the action is executed, and their role
is to make sure the request is "valid".
There are already a few Authorization filters inside the framework, the most
“famous” of which is the Authorize attribute that checks whether the current
user is allowed to execute the action.
7. ActionFilter
Action Filters are executed before and after an action is executed. One of the
core filters is the OutputCache filter, but you can find many other usages for
this filter. This is the most likely extension point you are going to use, as,
IMHO, it's critical to a good componentization of views: the controller only has
to do its main stuff, and all the other data needed by the view must be
retrieved from inside action filters.
8. ModelBinder
The default model binder maps HTTP parameters to action method parameters using
their names: a http parameter named user, address, city will be mapped to the
City property of the Address object that itself is a property of the method
parameter named user. The DefaultModelBinder works also with arrays, and other
list types.
But it can be pushed even further: for example you might use it to convert the
id of the person directly to the Person object looking up on the database.
9. ControllerBase
All controllers inherit from the base class Controller. A good way to
encapsulate logic or conventions inside your actions is to create you own layer
supertype and have all your controllers to inherit from it.
10. ResultFilter
Like the ActionFiters, the ResultFilters are execute before and after the
ActionResult is executed. Again, the OutputCache filter is an example of a
ResultFilter. The usual example that is done to explain this filter is logging.
If you want to log that a page has been returned to the user, you can write a
custom RenderFilter that logs after the ActionResult has been executed.
11. ActionResult
ASP.NET MVC comes with many different kind of results to render views, to render
JSON, plain text, files and to redirect to other actions. But if you need some
different kind of result you can write your own ActionResult and implement the ExecuteResult method.
For example, if you want to send a PDF file as result you could write your own
ActionResult that use a PDF library to generate the PDF.
12. ViewEngine
Probably you are not going to write your own view engine, but there are a few
that you might consider using instead of the default WebForm view engine. The
most interesting one, IMHO, is Spark.
13. HtmlHelper
Views must be very dumb and thin, and they should only have html markup and
calls to HtmlHelpers. There should be no code inside the views, so helpers come
very handy for extracting the code from the view and putting it into something
that is testable.