Custom Controller Factory in ASP.NET MVC
Join the DZone community and get the full member experience.
Join For FreeAfter discussing custom route handler and IRouteHandler as two extensibility points in ASP.NET MVC to customize the behavior of routing system, now I want to continue discussing thirteen major extensibility points in ASP.NET MVC by focusing on custom controller factories and building such controller factories.
When ASP.NET MVC receives a request, it needs to manage how to handle it with a specific controller and the action methods in it. The component that is responsible to map an incoming request to a specific controller and decide which controller to use is controller factory. There is a default controller factory in ASP.NET MVC that maps incoming requests to a controller with a Controller postfix.
The built-in controller factory is the registered controller factory by default and is implemented in DefaultControllerFactory class. Also it’s possible to extend its behavior with minor changes by deriving from this base class.
But in some circumstances you may need to have a fully customized behavior for your controller factories. One common example is when you use Dependency Injection frameworks where you need to use a customized factory. Fortunately, most of the DI frameworks provide such a customized controller factory out of the box, but if you were faced with a case to implement such a factory, you can implement the IControllerFactory interface and register your own custom controller factory.
IControllerFactory is an interface with two methods:
- CreateController: Getting the RequestContext instance and the string value of controller name, returns the controller to be used.
- ReleaseController: Gets a controller instance and releases this controller.
Implementation of a controller factory is comparatively easy, and can be done with less amount of work to be done.
In this post I implement a basic controller factory that loads controllers based on the user’s language, so a specific controller can be loaded for a specific language. In this sample application, I define the type name of controllers in web configuration file based on a pattern that corresponds to a specific culture, and implement a controller factory that loads the appropriate controllers group based on the client’s preferences.
First I define my type patterns in my configuration file as application settings. Here I have two cultures: if the user uses Farsi, then the Farsi controllers will be loaded, otherwise the default English language will be used.
<appSettings>
<add key="EnglishControllerTypePattern" value="IControllerFactorySample.Controllers.En.{0}"/>
<add key="FarsiControllerTypePattern" value="IControllerFactorySample.Controllers.Fa.{0}"/>
</appSettings>
Now I write my controller factory by implementing the IControllerFactory interface and its two methods.
using System;
using System.Configuration;
using System.Web.Mvc;
using System.Web.Routing;
namespace IControllerFactorySample.ControllerFactories
{
public class CustomControllerFactory : IControllerFactory
{
#region IControllerFactory Members
public IController CreateController(RequestContext requestContext, string controllerName)
{
if (string.IsNullOrEmpty(controllerName))
throw new ArgumentNullException("controllerName");
string language = requestContext.HttpContext.Request.Headers["Accept-Language"];
string controllerType = string.Empty;
if (language == "fa-IR")
controllerType = string.Format
(ConfigurationManager.AppSettings["FarsiControllerTypePattern"], controllerName);
else
controllerType = string.Format
(ConfigurationManager.AppSettings["EnglishControllerTypePattern"], controllerName);
IController controller = Activator.CreateInstance(Type.GetType(controllerType)) as IController;
return controller;
}
public void ReleaseController(IController controller)
{
if (controller is IDisposable)
(controller as IDisposable).Dispose();
else
controller = null;
}
#endregion
}
}
In the CreateController function, I detect the client’s language using the HTTP headers of the request, and load the appropriate type name based on the user’s culture. Then I use reflection APIs to load the type and create an instance of the controller to be returned. Note that this implementation doesn’t mandate the Controller postfix for controller names, so rather than defining my Home controller as HomeController class, I just can use Home name. I have defined my controllers in Fa and En sub-folders inside Controllers folder, so my controller factory can load them based on the type name patterns. Besides, in the ReleaseController method, I dispose the controller as expected.
The third and last step is to add this controller factory as the default factory to ASP.NET MVC. This can be done in Global.asax and its Application_Start method where I use ControllerBuilder.SetControllerFactory to add my factory type as the default controller factory to ASP.NET MVC.
using System.Web.Mvc;
using System.Web.Routing;
using IControllerFactorySample.ControllerFactories;
namespace IControllerFactorySample
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}
}
}
As you see, building a controller factory is very straightforward and you can get it done in a few simple steps. If I run the application and set my preferred language to Farsi, then Farsi controllers will be used to serve the requests, otherwise English controllers will be loaded.
As always, the sample application for this post is available for download.
Published at DZone with permission of Keyvan Nayyeri. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments