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:
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:
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:
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:
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)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn