I have a couple of questions related to this file (MVC-ControllerTypeCache.xml).
1) Can anyone tell me when and how this file is generated?
I know it is generated by the framework to reduce the amount of refection needed when controllers are called.
I also know there are a few internal class in MVC source for working with it, the controller factory GetControllerType
makes use of them.
2) Is there a way to work with it in an application ?
For example if I want to list all the controllers in the application, using this file would mean I do not have to find them all myself via reflection.
It would also be worth knowing how / when it gets updated as the method GetControllerType(requestContext, controllerName);
will return your controller type based on what it finds in this file.
Knowing when it gets updated and if you can rely on it could change the way you register controllers from plugins / modules that reside in their own assemblies.
I mainly ask purely out of interest though.
1) Can anyone tell me when and how this file is generated?
The DefaultControllerFactory.GetControllerType
which is invoked on each request calls the GetControllerTypeWithinNamespaces
method to retrieve the list of available controller types:
private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces) {
ControllerTypeCache.EnsureInitialized(BuildManager);
ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
... more code removed for brevity
}
As you can see it does 2 things in the beginning: initialization and retrieving of controller types from the ControllerTypeCache
.
The EnsureInitialized
method uses a singleton with double checked locking to ensure that initialization is performed only once for the entire lifetime of the application:
public void EnsureInitialized(IBuildManager buildManager) {
if (_cache == null) {
lock (_lockObj) {
if (_cache == null) {
List<Type> controllerTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(_typeCacheName, IsControllerType, buildManager);
var groupedByName = controllerTypes.GroupBy(
t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
StringComparer.OrdinalIgnoreCase);
_cache = groupedByName.ToDictionary(
g => g.Key,
g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
StringComparer.OrdinalIgnoreCase);
}
}
}
}
Notice how the _cache
field will be initialized only once if it is null. This will happen on the very first request that hits your site after the application has started by IIS.
The controller types are retrieved using the TypeCacheUtil.GetFilteredTypesFromAssemblies
method. So let's look into it:
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;
}
The code is pretty self explanatory:
TypeCacheSerializer
class to read them from the cache. Internally this serializer loads the file into a XmlDocument
and then manipulates its elements to extract the types.FilterTypesInAssemblies
method that will use reflection to retrieve the controller types from all referenced assemblies.Here's a blog post which also describes the process: http://www.beletsky.net/2011/12/inside-aspnet-mvc-instantiation-of.html
2) Is there a way to work with it in an application ?
You are not supposed to work directly with this XML file from your code because its contents and format could change in future versions which would break your code.
I agree though that it would have been nice to be able to take advantage of this feature from our code to improve the performance of otherwise expensive reflection code. I wish the authors of the framework had made this API public.
Unfortunately they haven't, so we could roll our own:
public static class ControllerTypeCache
{
private static object _syncRoot = new object();
private static Type[] _cache;
public static IEnumerable<Type> GetControllerTypes()
{
if (_cache == null)
{
lock (_syncRoot)
{
if (_cache == null)
{
_cache = GetControllerTypesWithReflection();
}
}
}
return new ReadOnlyCollection<Type>(_cache);
}
private static Type[] GetControllerTypesWithReflection()
{
var typesSoFar = Type.EmptyTypes;
var assemblies = BuildManager.GetReferencedAssemblies();
foreach (Assembly assembly in assemblies)
{
Type[] typesInAsm;
try
{
typesInAsm = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
typesInAsm = ex.Types;
}
typesSoFar = typesSoFar.Concat(typesInAsm).ToArray();
}
return typesSoFar
.Where(t => t != null &&
t.IsPublic &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t)
)
.ToArray();
}
}
It would also be worth knowing how / when it gets updated as the method GetControllerType(requestContext, controllerName); will return your controller type based on what it finds in this file.
This file never gets updated during the whole lifetime of the application. As explained earlier it is created once, when the application starts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With