Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How and when does MVC-ControllerTypeCache.xml get populated

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.

like image 467
David McLean Avatar asked Aug 21 '12 14:08

David McLean


1 Answers

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:

  1. it uses a 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.
  2. if nothing is found into the cache an expensive call is made to the FilterTypesInAssemblies method that will use reflection to retrieve the controller types from all referenced assemblies.
  3. it saves the types to the cache so that next time they are loaded from the cache.

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.

like image 154
Darin Dimitrov Avatar answered Oct 20 '22 02:10

Darin Dimitrov