Ways to Extend The ASP.NET MVC 5 Framework
ASP.NET MVC 5 Framework does not limit you to the capabilities it provide out of the box. It is an extensible framework which emphasizes on convention over configuration or coding by convention, and below are some ways you can extend it.
-
Action Results
- Action results are returned by the controller (e.g. return View()).
- They are responsible for writing response to a request.
- They are executed internally by MVC to write out the response.
- You can create a custom action result by inheriting from ActionResult and overriding ExecuteResult(), or by extending an existing action result (e.g. FileResult).
-
Action Filters
- Action filters execute before and after a controller action method.
- You can create a custom action filter by implementing ActionFilter’s OnActionExecuting() and OnActionExecuted() methods.
- They can be applied at the method, controller, or global scope.
-
Helper Methods
- Two types: inline Razor helpers (@helper) and HTML helpers (@Html.*).
- They reduce code duplication but if it involves complex logic consider using partial view / child action method.
- You can create your own HTML helper by creating an extension method to HTMLHelper.
-
View Engines
- When controller returns a view, the MVC executes the action result and in the process uses a view engine to find a view and returns a ViewEngineResult whose Render() method is called.
- A custom view engine can be used to extend view selection functionality such as to implement swappable UI themes (e.g. Wordpress themes).
- You create a custom view engine by extending the RazorViewEngine and in the constructor setting the search location format properties to use the theme directories that you want the view engine to search for for the currently active theme.
- You register the custom view engine in Application_Start() calling ViewEngines.Engines.Insert(), inserting it as the first engine to be evaluated. You can leave the RazorViewEngine there if you want a fallback. Note that MVC supports multiple view engines.
-
Exception Filters
- Exception filters catch errors that occur inside the scope of action method execution (including inside action filters and action results) and thus provide more contextual framework information.
- The default exception filter in MVC is the HandleErrorAttribute.
- You can create a custom exception filter by implementing the IExceptionFilter’s OnException().
- They can be applied at the method, controller, or global scope.
- The ExceptionHandled flag is useful when using multiple exception filters: checking to make sure exception was not handled by previous exception filter before continuing.
- Application_Error() should still be implemented as a fallback.
-
Server-Side Validations
- Two ways: via data attributes (e.g. Required, Range, etc.) and implementation of IValidatableObject’s Validate() in the model class, the former being reusable while the latter providing easy cross property validation.
- The model binder that binds the request data to the model classes (or the action method parameters) checks the data attributes first then the Validate() and populates the controller’s ModelState property which you can check if any errors occurred in the model binding process (e.g. ModelState.IsValid).
- You can create a custom data attribute by inheriting from ValidationAttribute and overriding IsValid() and setting the ErrorMessage in the constructor.
- You can implement the IValidatableObject’s Validate() in the model class and return List containing any error messages.
-
Model Binders
- A model binder implements the IModelBinder’s BindModel(). The binder model is returned by a model binder provider that implements IModelBinderProvider’s GetBinder(). The model binding process searches through a list of model binder providers asking them if they can provide a model binder for the data it is trying to bind.
- You can create a custom model binder by implementing IModelBinder’s BindModel() and adding any error messages to the controller’s ModelState. Then you create a custom model binder provider by implementing IModelBinderProvider’s GetBinder() to return the custom model binder you just created. You register your custom model binder provider in Application_Start() by calling ModelBinderProviders.BinderProviders.Insert().
-
Value Providers
- A model binder employs value providers to provide it data from different sources such as posted form data, query string, route data, etc. that it can use to populate the action method parameters.
- Possible uses of a custom value provider is to provide HTTP header data or settings from configuration file or database.
- You create a custom value provider by implementing IValueProvider’s two methods: ContainsPrefix() to return true if the passed in property name matches any data the custom value provider provides and GetValue() to return the value itself. Then you create a custom value provider factory by inheriting from ValueProviderFactory and overriding GetValueProvider() to return the custom value provider you just created. You register your custom value provider factory in Application_Start() by calling ValueProviderFactories.Factories.Insert().
-
Authentication Filters and Authorization Filters
- Authentication filters implement IAuthenticationFilter’s two methods: OnAuthentication() to authenticate a request and OnAuthenticationChallenge() that runs when authentication fails and right after action method execution.
- Authorization filters implement IAuthorizationFilter’s OnAuthorization().
- These filters return a non-null context results if authentication/authorization fails.
- Note that MVC supports Forms and Windows authentication by default and does not support Basic authentication. One use of a custom authentication filter is to provide Basic authentication.
- You create a custom authentication filter by implementing IAuthenticationFilter. For custom authorization filter, consider inheriting from AuthorizeAttribute instead.
- Action Name Selectors and Action Method Selectors
- Action name selectors (e.g. [ActionName(“xxxxx”)]) and action method selectors (e.g. [HttpGet] and [HttpPost]) influence the decision process of selecting which action method to handle the request by the action invoker.
- You can create a custom action name selector by inheriting from ActionNameSelectorAttribute and overriding IsValidName().
- You can create a custom action method selector by inheriting from ActionMethodSelectorAttribute and overriding IsValidForRequest().
Additional References:
- ASP.NET MVC 5 Application Lifecycle Diagram - this will give you an idea how MVC 5 works
- A look inside MVC - a deep dive inside MVC (older version), but still worth taking a look
- MVC 5 Source Code - if you’re up to it why not pull the source code from GitHub
BONUS: Found this good article worth looking too: 10 ways to extend your Razor views in ASP.NET core - the complete overview.