Inside ASP.NET MVC: Instantiation of Controller
Controller are being created by ControllerFactory
, by default IControllerFactory
type is being resolved to DefaultControllerFactory
. Today’s post is dedicated to some details of how actually DefaultControllerFactory
works and creates instance of required controller. Let’s go from the beginning!
Request for controller instance
Initially, we are at MvcHandler’s ProcessRequestInit
method, where we extract controllers name from RouteData and request controller factory to create corresponding controller.
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
// If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
// at Request.Form) to work correctly without triggering full validation.
bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current);
if (isRequestValidationEnabled == true) {
ValidationUtility.EnableDynamicValidation(HttpContext.Current);
}
AddVersionHeader(httpContext);
RemoveOptionalRoutingParameters();
// Get the controller type
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
// Instantiate the controller and call Execute
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}
DefaultControllerBuilder internals
CreateController
is rather elegant method, basically it does some arguments check’s, resolve the controller type by it’s name and then call to GetControllerIntance
to instantiate that type.
public virtual IController CreateController(RequestContext requestContext, string controllerName) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = GetControllerInstance(requestContext, controllerType);
return controller;
}
Getting the controller type
GetControllerType
is delegating it’s call to internal GetControllerTypeWithinNamespaces
. It receives Route, Controller’s name and Namespaces.
private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces) {
// Once the master list of controllers has been created we can quickly index into it
ControllerTypeCache.EnsureInitialized(BuildManager);
ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
switch (matchingTypes.Count) {
case 0:
// no matching types
return null;
case 1:
// single matching type
return matchingTypes.First();
default:
// multiple matching types
throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);
}
}
The namespaces parameter are quite important. If you remember, you can explicitly mention the namespace during the route definition, with overloaded MapRoute method:
public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces) {
return MapRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces);
}
Namespaces parameter then being stored to Route.DataTokens[“Namespaces”]. Namespaces parameter matters, then you have controllers with same names. This is in particular makes sense then you have different areas.
Caching Controller Types
The interesting thing is that framework not re-reads types for each request, that would be just to expesive, but instead it uses a cache which is initialized at the very first request and used at the life time of application. The call ControllerTypeCache.EnsureInitialized(BuildManager);
makes sure that cache is in actual state. How does MVC caching the types?
Very simple and straight forward solution - in .XML file.
public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager) {
TypeCacheSerializer serializer = new TypeCacheSerializer();
// first, try reading from the cache on disk
List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
if (matchingTypes != null) {
return matchingTypes;
}
// if reading from the cache failed, enumerate over every assembly looking for a matching type
matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();
// finally, save the cache back to disk
SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);
return matchingTypes;
}
It try’s read cache from file, if there are not matching types there it would try to read assemblies and then store it to cache. You might be interested where this file is actually located, so take a look:
And for the most curious guys, here is the content:
<?xml version="1.0" encoding="utf-8"?>
<!--This file is automatically generated. Please do not modify the contents of this file.-->
<typeCache lastModified="14.10.2011 19:33:03" mvcVersionId="aa5414f4-4d8e-4f2a-a98b-7334bf15d104">
<assembly name="MvcForDebug2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<module versionId="1ad99820-dc17-4be0-9f56-6dd2bdcd7950">
<type>MvcForDebug2.Controllers.HomeController</type>
</module>
</assembly>
</typeCache>
FilterTypesInAssemblies
method tries to get all controllers it can. What it does, it goes throught all referenced assemblies and using special predicate class matching the types.
private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> predicate) {
// Go through all assemblies referenced by the application and search for types matching a predicate
IEnumerable<Type> typesSoFar = Type.EmptyTypes;
ICollection assemblies = buildManager.GetReferencedAssemblies();
foreach (Assembly assembly in assemblies) {
Type[] typesInAsm;
try {
typesInAsm = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex) {
typesInAsm = ex.Types;
}
typesSoFar = typesSoFar.Concat(typesInAsm);
}
return typesSoFar.Where(type => TypeIsPublicClass(type) && predicate(type));
}
So, the last interesting thing here is the predicate that actually used:
internal static bool IsControllerType(Type t) {
return
t != null &&
t.IsPublic &&
t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t);
}
You can see, it tries to match any public, ends with “Controller” not abstract and implementing IController
interface. That’s why important do not forget to call all your controller with “Controller” suffix (yes, I did that mistake several times in the beginning of my MVC journey).
Note, that if you have 2 controllers with same names in different namespaces, but did not provide namespace constraint you will have several matchingTypes
, so the CreateAmbiguousControllerException
will be thrown. I believe each of us seen that kind of exception at least once.
Instantiating the Type
As we go a little back and check the code of CreateController
: now, we’ve got the type (or null if type has not been resolved). Next thing is to instantiate it. Nothing really fancy here:
protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
if (controllerType == null) {
throw new HttpException(404,
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_NoControllerFound,
requestContext.HttpContext.Request.Path));
}
if (!typeof(IController).IsAssignableFrom(controllerType)) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
controllerType),
"controllerType");
}
return ControllerActivator.Create(requestContext, controllerType);
}
If everything is all right with controller type it ask ControllerActivator to create the instance. In default case, ControllerActivator is DefaultControllerActivator
:
public IController Create(RequestContext requestContext, Type controllerType) {
try {
return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
}
catch (Exception ex) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ErrorCreatingController,
controllerType),
ex);
}
}
As for the rest of MVC entities it would use IDependencyResolver
to resolve that type. IDependecyResolved
is detailed here, but as you remember by default at the very end it calls Activator.CreateInstance(type)
method.
Conclusions
New controller is instantiated on each HTTP request. The instantiation is in-directed, means first we retrieve Type, after creating Instance. The type is being searched dynamically by getting out reflection information from assemblies. To optimize the performance of ASP.NET MVC application, the internal cache of types is used. The cache is stored in file in “Temporary ASP.NET Files\root\cdd53039\36a27802\UserCache\MVC-ControllerTypeCache.xml” folder. Namespaces of controllers are matters, in case of two or more controllers has the same name but different namespaces the exception would be thrown. In case of controller type could not be resolved, the HTTP 404 response is generated. Otherwise, the controller instance would be created by IControllerActivator
instance.
Previous post: Inside ASP.NET MVC: IDependencyResolver - Service locator in MVC
Controller are being created by ControllerFactory
, by default IControllerFactory
type is being resolved to DefaultControllerFactory
. Today’s post is dedicated to some details of how actually DefaultControllerFactory
works and creates instance of required controller. Let’s go from the beginning!
Request for controller instance
Initially, we are at MvcHandler’s ProcessRequestInit
method, where we extract controllers name from RouteData and request controller factory to create corresponding controller.
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks // at Request.Form) to work correctly without triggering full validation. bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current); if (isRequestValidationEnabled == true) { ValidationUtility.EnableDynamicValidation(HttpContext.Current); } AddVersionHeader(httpContext); RemoveOptionalRoutingParameters(); // Get the controller type string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute factory = ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(RequestContext, controllerName); if (controller == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, factory.GetType(), controllerName)); } }
DefaultControllerBuilder internals
CreateController
is rather elegant method, basically it does some arguments check’s, resolve the controller type by it’s name and then call to GetControllerIntance
to instantiate that type.
public virtual IController CreateController(RequestContext requestContext, string controllerName) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (String.IsNullOrEmpty(controllerName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName"); } Type controllerType = GetControllerType(requestContext, controllerName); IController controller = GetControllerInstance(requestContext, controllerType); return controller; }
Getting the controller type
GetControllerType
is delegating it’s call to internal GetControllerTypeWithinNamespaces
. It receives Route, Controller’s name and Namespaces.
private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces) { // Once the master list of controllers has been created we can quickly index into it ControllerTypeCache.EnsureInitialized(BuildManager); ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces); switch (matchingTypes.Count) { case 0: // no matching types return null; case 1: // single matching type return matchingTypes.First(); default: // multiple matching types throw CreateAmbiguousControllerException(route, controllerName, matchingTypes); } }
The namespaces parameter are quite important. If you remember, you can explicitly mention the namespace during the route definition, with overloaded MapRoute method:
public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces) { return MapRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces); }
Namespaces parameter then being stored to Route.DataTokens[“Namespaces”]. Namespaces parameter matters, then you have controllers with same names. This is in particular makes sense then you have different areas.
Caching Controller Types
The interesting thing is that framework not re-reads types for each request, that would be just to expesive, but instead it uses a cache which is initialized at the very first request and used at the life time of application. The call ControllerTypeCache.EnsureInitialized(BuildManager);
makes sure that cache is in actual state. How does MVC caching the types?
Very simple and straight forward solution - in .XML file.
public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager) { TypeCacheSerializer serializer = new TypeCacheSerializer(); // first, try reading from the cache on disk List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer); if (matchingTypes != null) { return matchingTypes; } // if reading from the cache failed, enumerate over every assembly looking for a matching type matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList(); // finally, save the cache back to disk SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer); return matchingTypes; }
It try’s read cache from file, if there are not matching types there it would try to read assemblies and then store it to cache. You might be interested where this file is actually located, so take a look:
And for the most curious guys, here is the content:
<?xml version="1.0" encoding="utf-8"?> <!--This file is automatically generated. Please do not modify the contents of this file.--> <typeCache lastModified="14.10.2011 19:33:03" mvcVersionId="aa5414f4-4d8e-4f2a-a98b-7334bf15d104"> <assembly name="MvcForDebug2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <module versionId="1ad99820-dc17-4be0-9f56-6dd2bdcd7950"> <type>MvcForDebug2.Controllers.HomeController</type> </module> </assembly> </typeCache>
FilterTypesInAssemblies
method tries to get all controllers it can. What it does, it goes throught all referenced assemblies and using special predicate class matching the types.
private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> predicate) { // Go through all assemblies referenced by the application and search for types matching a predicate IEnumerable<Type> typesSoFar = Type.EmptyTypes; ICollection assemblies = buildManager.GetReferencedAssemblies(); foreach (Assembly assembly in assemblies) { Type[] typesInAsm; try { typesInAsm = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { typesInAsm = ex.Types; } typesSoFar = typesSoFar.Concat(typesInAsm); } return typesSoFar.Where(type => TypeIsPublicClass(type) && predicate(type)); }
So, the last interesting thing here is the predicate that actually used:
internal static bool IsControllerType(Type t) { return t != null && t.IsPublic && t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && !t.IsAbstract && typeof(IController).IsAssignableFrom(t); }
You can see, it tries to match any public, ends with “Controller” not abstract and implementing IController
interface. That’s why important do not forget to call all your controller with “Controller” suffix (yes, I did that mistake several times in the beginning of my MVC journey).
Note, that if you have 2 controllers with same names in different namespaces, but did not provide namespace constraint you will have several matchingTypes
, so the CreateAmbiguousControllerException
will be thrown. I believe each of us seen that kind of exception at least once.
Instantiating the Type
As we go a little back and check the code of CreateController
: now, we’ve got the type (or null if type has not been resolved). Next thing is to instantiate it. Nothing really fancy here:
protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { throw new HttpException(404, String.Format( CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_NoControllerFound, requestContext.HttpContext.Request.Path)); } if (!typeof(IController).IsAssignableFrom(controllerType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase, controllerType), "controllerType"); } return ControllerActivator.Create(requestContext, controllerType); }
If everything is all right with controller type it ask ControllerActivator to create the instance. In default case, ControllerActivator is DefaultControllerActivator
:
public IController Create(RequestContext requestContext, Type controllerType) { try { return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType)); } catch (Exception ex) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_ErrorCreatingController, controllerType), ex); } }
As for the rest of MVC entities it would use IDependencyResolver
to resolve that type. IDependecyResolved
is detailed here, but as you remember by default at the very end it calls Activator.CreateInstance(type)
method.
Conclusions
New controller is instantiated on each HTTP request. The instantiation is in-directed, means first we retrieve Type, after creating Instance. The type is being searched dynamically by getting out reflection information from assemblies. To optimize the performance of ASP.NET MVC application, the internal cache of types is used. The cache is stored in file in “Temporary ASP.NET Files\root\cdd53039\36a27802\UserCache\MVC-ControllerTypeCache.xml” folder. Namespaces of controllers are matters, in case of two or more controllers has the same name but different namespaces the exception would be thrown. In case of controller type could not be resolved, the HTTP 404 response is generated. Otherwise, the controller instance would be created by IControllerActivator
instance.
Previous post: Inside ASP.NET MVC: IDependencyResolver - Service locator in MVC