Custom Controller Factory in ASP.NET MVC

Posted by: Mahesh Sabnis , on 4/7/2013, in Category ASP.NET MVC
Views: 29615
Abstract: It’s rather easy to setup a Custom Controller Factory in ASP.NET MVC. This articles explores a scenario where you need to log requests from users in an MVC application and you would like to pass the Logger object to each and every Controller in the system

In any MVC framework, the controller is the glue between your View and Logic. The controller object is created by the default controller factory object using the constructor with no parameters. The below diagram shows request processing using controller factory:

 

requestprocessing

However, in most Dependency Injection scenarios, we like to remove the responsibility of ‘instantiating’ a dependency from the controller and instead injecting it via the Constructor. But given that the default Controller Factory can work with only parameter-less constructors, we get restricted from using DI! Fear not, the solution is not very difficult.

Today we’ll consider a scenario where you need to log requests from users in an MVC application and you would like to pass the Logger object to each and every Controller in the system. To achieve this, we simply need to create the controller object via a custom controller factory.

Please note: The Logger scenario is over-simplified for purpose of the demo here. As mentioned earlier, a Custom Controller Factory is mighty useful for creating a DI container.

Writing your own Custom Controller Factory

Let’s go ahead and create a Custom Controller Factory that injects a Logger into each controller of our MVC application.

The Sample DB and the Data Layer

For this demonstration, the LoggerInformation table is created in Sql Server into the Company database. The script is as below:

USE [Company]
GO
 
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[LoggerInformation](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserName] [varchar](50) NOT NULL,
    [RequestUrl] [varchar](50) NOT NULL,
    [Browser] [varchar](50) NOT NULL,
    [RequestType] [varchar](50) NOT NULL,
    [UserHostAddress] [varchar](50) NOT NULL,
CONSTRAINT [PK_LoggerInformation] PRIMARY KEY CLUSTERED
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

Step 1: Open VS 2012 and create a new MVC project. Name it as ‘MVC40_CustomController’. In the project, add the ADO.NET EF in the Model folder for the LoggerInformation table from SQL Server. After completing the wizard, the EF model will be as shown below:

loggerinformation

Step 2: In the Model folder, add a new class file called ‘DataClasses.cs’ and add the following code in it:

using System.Collections.Generic;

namespace MVC40_CustomController.Models
{
public class EmployeeInfo
{
  public int EmpNo { get; set; }
  public string EmpName { get; set; }
  public string DeptName { get; set; }
  public string Designation { get; set; }
  public int  Salary { get; set; }
}
public class DataAccess
{
  List<EmployeeInfo> lstEmps = new List<EmployeeInfo>();
  public DataAccess()
  {
   lstEmps.Add(new EmployeeInfo() {EmpNo=1,EmpName="A",DeptName="D1",Designation="TL",Salary=45000 });
   lstEmps.Add(new EmployeeInfo() { EmpNo = 2, EmpName = "B", DeptName = "D1", Designation = "TL", Salary = 45000 });
   lstEmps.Add(new EmployeeInfo() { EmpNo = 3, EmpName = "C", DeptName = "D2", Designation = "PM", Salary = 55000 });
   lstEmps.Add(new EmployeeInfo() { EmpNo = 4, EmpName = "D", DeptName = "D2", Designation = "PM", Salary = 55000 });
   lstEmps.Add(new EmployeeInfo() { EmpNo = 5, EmpName = "E", DeptName = "D3", Designation = "PH", Salary = 65000 });
   lstEmps.Add(new EmployeeInfo() { EmpNo = 6, EmpName = "F", DeptName = "D3", Designation = "PH", Salary = 65000 });
  }
  public List<EmployeeInfo> GetEmps()
  {
   return lstEmps;
  }
}
}

The Custom Logger

Step 3: In the project, add a new folder and name it as ‘LoggerInfo’. In this folder, add a new class file and name it as ‘LoggerInfo.cs’. Add the following code in it:

using MVC40_CustomController.Models;

namespace MVC40_CustomController.LoggerInfo
{
/// <summary>
/// Interface for the Logging
/// </summary>
public interface IRequestLogger
{
  void RecordLog(LoggerInformation logInfo);
}
/// <summary>
/// The class for Loggin the Request Information into the database usign ADO.NET EF
/// </summary>
public class RequestLogger : IRequestLogger
{
  CompanyEntities objContext;
  public RequestLogger()
  {
   objContext = new CompanyEntities();
  }

  public void RecordLog(LoggerInformation logInfo)
  {
   objContext.AddToLoggerInformation(logInfo);
   objContext.SaveChanges();
  }
}
}

The above code shows us the interface IRequestLogger and its implementation RequestLogger. The Interface has a single method to start off with, the ‘RecordLog’ method. This method accepts the LoggerInformation object received from the ADO.NET EF.

Creating a XML Map of our Controllers

Step 4: In the project, add a new xml file. Name it as ‘Controllers.xml’. This file contains information of the controller like the controller class name and the full qualified path of the controller class.

<?xml version="1.0" encoding="utf-8" ?>
<Controllers>
<ControllerName>
  <FullPath>MVC40_CustomController.Controllers.EmployeeInfoController,MVC40_CustomController 
  </FullPath>
  <Class>EmployeeInfo</Class>
</ControllerName>
<ControllerName>
  <FullPath>MVC40_CustomController.Controllers.HomeController,MVC40_CustomController
  </FullPath>
  <Class>Home</Class>
</ControllerName>
</Controllers>

Writing the Controller Factory

Step 5: In the project, add a new folder, name it as ‘CustomControllerFactoryRepository’. In this folder add a new class file, name it as ‘CCustomControllerFactory.cs’. Add the below code in it:

using MVC40_CustomController.LoggerInfo;
namespace MVC40_CustomController.CustomControllerFactoryRepository
{
/// <summary>
/// The Custom Controller Factory class.
///
/// </summary>
public class CCustomControllerFactory : IControllerFactory
{
  /// <summary>
  /// The method to create the controller object from the requested routing information.
  /// This methods loads the xml file where the Controller information
  /// with its full qualified name stored. The XLinq is used to load the xml file.
  /// The xml file is queried using xlinq based upon the controllerName.
  /// The information received from the cml file is stored into the Dictionary <string, string> object.
  /// From this dictionary object the controller object is created and using the Activator object
  /// the controller instance is created to whcih the Logger object is passed.
  /// </summary>
  /// <param name="requestContext"></param>
  /// <param name="controllerName"></param>
  /// <returns></returns>
  public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
  {
   IController controllerType = null;
   Type typeData = null;
   XDocument xdoc = XDocument.Load(HostingEnvironment.MapPath(@"~/Controllers.xml"));
   var controllerData =( from controller in xdoc.Descendants("ControllerName")
    select new ControllerInfo()
    {
     ControllerKey = controller.Descendants("Class").First().Value,
     ControllerPath = controller.Descendants("FullPath").First().Value
    }).ToList();
    Dictionary<string, string> controllersDictionary = new Dictionary<string, string>();
    foreach (var item in controllerData)
    {
     controllersDictionary.Add(item.ControllerKey, item.ControllerPath);
    }
    string controllerTypeName = null;
    if (controllersDictionary.TryGetValue(controllerName, out controllerTypeName))
    {
     typeData = Type.GetType(controllerTypeName);
    }
    IRequestLogger logger = new RequestLogger();
    controllerType = (IController)Activator.CreateInstance(typeData, logger);
    return controllerType;
   }
   /// <summary>
   /// The default session state
   /// </summary>
   /// <param name="requestContext"></param>
   /// <param name="controllerName"></param>
   /// <returns></returns>
   public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName)
   {
    return SessionStateBehavior.Default;
   }
   /// <summary>
   /// Release the controller
   /// </summary>
   /// <param name="controller"></param>
   public void ReleaseController(IController controller)
   {
    IDisposable release = controller as IDisposable;
    release.Dispose();
   }
  }
  /// <summary>
  /// The Class for Controller Information
  /// </summary>
  public class ControllerInfo
  {
   public string ControllerKey { get; set; }
   public string ControllerPath { get; set; }
  }
}

As we can see above, the CreateController method is the heart of the controller factory. It receives context information in form of the ControllerName and the RequestContext. This method then loads the xml file created in Step 4 and retrieves the controller information from it. Once we have the Controller information, we use Reflection to create an instance of the controller type. The CreateInstance method of the Activator object accepts the RequestLogger object along with the type object of the controller. This is then passed to the controller constructor which has the IRequestLogger as input parameter.

Using the Custom Controller Factory

Step 6: In the Controller folder, add a new controller of name EmployeeInfoController, implement it as below:

using MVC40_CustomController.LoggerInfo;
using MVC40_CustomController.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace MVC40_CustomController.Controllers
{
public class EmployeeInfoController : Controller
{
  DataAccess objDs;
  List<EmployeeInfo> filteredEmps;
  IRequestLogger logger;
  /// <summary>
  /// The Constructor with the Logger parameter.
  /// </summary>
  /// <param name="log"></param>
   public EmployeeInfoController(IRequestLogger log)
   {
    objDs = new DataAccess();
    logger = log;
   }
   //
   // GET: /EmployeeInfo/
   public ActionResult Index()
   {
    LogInfo();
    var Emps = from e in objDs.GetEmps()
    select e;
    filteredEmps = Emps.ToList();
    return View(filteredEmps);
   }
   /// <summary>
   /// Private method for storing the Loggin information
   /// </summary>
   private void LogInfo()
   {
    LoggerInformation logInfo = new LoggerInformation();
    logInfo.UserName = this.Request.LogonUserIdentity.Name;
    logInfo.RequestUrl = this.Request.Url.AbsoluteUri;
    logInfo.Browser = this.Request.Browser.Browser;
    logInfo.RequestType = this.Request.RequestType;
    logInfo.UserHostAddress = this.Request.UserHostAddress;
    logger.RecordLog(logInfo);
   }
  }
}

If you observe the above code, the EmployeeInfoController accepts the IRequectLogger as input parameter, the object for the RequestLogger is received from the CCustomControllerFactory. The private helper method of name LogInfo is responsible for the getting the request information e.g. UserName, Browser etc.

Step 7: In the global.asax add the following line to the Application_Start() method to tell MVC about our custom controller factory.

ControllerBuilder.Current.SetControllerFactory(typeof(CCustomControllerFactory));

Step 8: Apply the breakpoint on the EmployeeInfoController constructor, run the application and enter the request URL for EmployeeInfo controller. The debug breakpoint will be as below:

debug

If you see the debug information, RequestLogger object is available with the constructor. Complete the debugging, you will get the logger information stored into the database table as below:

log-info

Conclusion

It’s rather easy to setup a Custom Controller Factory in ASP.NET MVC. The Factory can then inject specific logic into the controller through constructor injection.

Download the entire source code of this article (Github)

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Nico on Wednesday, February 5, 2014 8:34 AM
Cool article but I noticed you add a hard-core dependency in the factory:
IRequestLogger logger = new RequestLogger();

Would it not be better to inject it ?
I mean I understand you used the controller factory to inject dependency in the controller, but what is the point if higher on, you add this dependency with this instance? How could we for example change the instance? We would have to go back to the factory code to change it...

Otherwise good article to understand how to create a controller factory and why we would use it.
Comment posted by dkbose on Tuesday, July 22, 2014 7:42 PM
This is the ultimate article!

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel