次の方法で共有


WebActivator – Under the hood

I have been seeing this little library being more increasingly used in recent packages. More recently I see this used in Unity BootStrapper for MVC.

For those unfamiliar with WebActivator it’s an incredibly useful tool to have if you are a packager or a framework developer. Read more: https://blogs.msdn.com/b/davidebb/archive/2010/10/11/light-up-your-nupacks-with-startup-code-and-webactivator.aspx

It basically provides three methods – PreStart(), PostStart and ShutDown() to which we can hook any custom code. What’s the big deal, we can do this in Global.asax? Well, this becomes incredibly useful when one is making their own package and would like to perform certain actions when application starts or shuts down but don’t want the package consumer to do these actions manually!

This little library really intrigued me on how it is possible to achieve it without hooking into the consuming code – so decided to perform a little bit of investigation to understand how it works and here is what I found.

Step One: Long time - no see! Global Attributes.

Global attributes have been in .NET for a very long time, but I have not seen them used much except for defining the usual title, description, version, etc AssemblyInfo.cs as shown in below example:

 [assembly: AssemblyTitle("WindowsApplication1")]

More: https://msdn.microsoft.com/en-us/library/284c1c4s(v=vs.90).aspx

This is what we find in WebActivator assembly:

 [assembly: System.Web.PreApplicationStartMethod(typeof(ActivationManager), "Run")] 

Step Two: What is PreApplicationStartMethod?

Turns out that this attribute in System.Web namespace was added in .NET 4.0 and is the key to functioning of WebActivator. The PreApplicationStartMethodAttribute constructor takes two parameters. First parameter is the Type of the class required to be called before an application starts and the second parameter is a string that represents the static method within the type to be called.

More: https://msdn.microsoft.com/en-us/library/system.web.preapplicationstartmethodattribute(v=vs.110).aspx

So we head to ActivationManager > Run method.

Step Three: Unravelling the mystery.

As I slowly understand the code written in ActivationManager’s static Run method, things become a little clearer.

public static void Run()
        {
            if (!ActivationManager._hasInited)
            {
                bool flag = Type.GetType("Mono.Runtime") != null;
                if (flag)
                {
                    ActivationManager.RunPreStartMethods(false);
                }
                else
                {
                    ActivationManager.RunPreStartMethods(ActivationManager.IsInClientBuildManager());
                }
                if (HostingEnvironment.IsHosted)
                {
                    Type typeFromHandle = typeof(ActivationManager.StartMethodCallingModule);
                    if (flag)
                    {
                        HttpModuleActionCollection modules = (WebConfigurationManager.GetWebApplicationSection("system.web/httpModules") as HttpModulesSection).Modules;
                        modules.Add(new HttpModuleAction(typeFromHandle.FullName, typeFromHandle.AssemblyQualifiedName));
                    }
                    else
                    {
                        DynamicModuleUtility.RegisterModule(typeFromHandle);
                    }
                }
                else
                {
                    ActivationManager.RunPostStartMethods();
                }
                ActivationManager._hasInited = true;
            }
        }

 

Ok so there is a checkfor Mono.Runtime. Then the PreStartMethods are called. Obvious takeaway is hosting on mono is different and hence the flag.  

 RunPreStartMethods calls ActivationManager.RunActivationMethods<PreApplicationStartMethodAttribute>(designerMode); 

As shown below this code scans all the assemblies for all custom PreApplicationStartMethod attributes, orders them and invokes the methods. The same scheme of method invocation is true for PostApplicationStartMethodAttribute and ApplicationShutdownMethodAttribute, the difference is only when they are called. 

private static void RunActivationMethods<T>(bool designerMode = false) where T : BaseActivationMethodAttribute
        {
            IOrderedEnumerable<T> orderedEnumerable = 
                from att in ActivationManager.Assemblies.Concat(ActivationManager.AppCodeAssemblies).SelectMany((Assembly assembly) => assembly.GetActivationAttributes<T>())
                orderby att.Order
                select att;
            foreach (T current in orderedEnumerable)
            {
                if (!designerMode || current.ShouldRunInDesignerMode())
                {
                    current.InvokeMethod();
                }
            }
        }

That kind of solves the puzzle of how PreStartMethod is works.

Step four: Post Start Method and let’s shutdown!

The PostApplicationStartMethodAttribute attribute allows to execute code after Application_Start method in Global.asax. But how is it determined. If we look back at the code in static Run method of ActivationManager class we will notice StartMethodCallingModule being registered. This is custom Http Module implementing IHttpModule interface with two methods - Init() and Dispose().

Inside Init() method RunPostStartMethods() is called. Also interesting to note here is that if the application is not hosted on IIS, activation method in this attribute will be invoked immediately after PreApplicationStartMethod.

Since HTTPModules may call Init() method more than once, there is a counter _initializedModuleCount inside a lock statement to ensure its called only once.

And now for the last attribute, ApplicationShutdownMethodAttribute, it’s called in Custom HTTPModule’s Dispose() method and the counter now runs in reverse and the shutdown methods will be invoked on dispose of the last module.

private class StartMethodCallingModule : IHttpModule
{
    private static object _lock = new object();
    private static int _initializedModuleCount;
    public void Init(HttpApplication context)
    {
        lock (ActivationManager.StartMethodCallingModule._lock)
        {
            if (ActivationManager.StartMethodCallingModule._initializedModuleCount++ == 0)
            {
                ActivationManager.RunPostStartMethods();
            }
        }
    }
    public void Dispose()
    {
        lock (ActivationManager.StartMethodCallingModule._lock)
        {
            if (--ActivationManager.StartMethodCallingModule._initializedModuleCount == 0)
            {
                ActivationManager.RunShutdownMethods();
            }
        }
    }
}

If you are a custom package developer, this tool will come really handy. It can be downloaded from Nuget. The source is available at https://github.com/davidebbo/WebActivator