In the previous blog we discussed how to receive requests from the HTTP stack. In this post I’ll discuss how to host your MVC project.

When hosting your project in IIS, your application is typically bound to a dedicated application pool (AppDomain). In theory the web application could be run in the already running default app domain, however, in this post I’ll try to stay as close as possible to the IIS approach.

System.Web contains the class ApplicationHost which allows you to do exactly what we want: create a new AppDomain, and specify a user supplied type (class) which should be instanced there to bootstrap the domain.

Again, in theory, you could use any class, but there are two things to keep in mind:

  • you want to communicate with the new AppDomain from your default AppDomain
  • you don’t want this bootstrapping instance to be garbage collected as it’s the root object in the new domain

The classic solution for the first issue is to use a class derived from MarshalByRefObject, as this will automagically enable remoting by RPC between your AppDomains. Another – more modern – option would be to use WCF, but I didn’t check if that works in practice yet.

The second issue is fixed by explicitly telling the remoting infrastructure the object’s lease is never expiring (which ties its actual lifetime to the lifetime of the new AppDomain).

The code below demonstrates this:

public class AppHost : MarshalByRefObject
{
    //factory method
    private static AppHost GetHost(string virtualDir, string physicalPath)
    {
        // Fix for strange CreateApplicationHost behavior (can only resolve assembly when in GAC or bin folder)
        if (!(physicalPath.EndsWith("\"))) physicalPath += "\";

        // Copy this hosting DLL into the /bin directory of the application
        var fileName = Assembly.GetExecutingAssembly().Location;
        try
        {
            var folderName = string.Format("{0}bin\", physicalPath);

            //create folder if it doesn't exist
            if (!Directory.Exists(folderName)) Directory.CreateDirectory(folderName);

            //copy file
            File.Copy(fileName, Path.Combine(folderName, Path.GetFileName(fileName)), true);

            //and all assemblies
            var pathToAppHost = Path.GetDirectoryName(fileName);
            foreach (var fn in Directory.EnumerateFiles(pathToAppHost, "*.dll", SearchOption.TopDirectoryOnly))
            {
                File.Copy(fn, Path.Combine(folderName, Path.GetFileName(fn)), true);
            }
        }
        catch { }

        return (AppHost)ApplicationHost.CreateApplicationHost(typeof(AppHost), virtualDir, physicalPath);
    }

    //set an infinite lease
    public override object InitializeLifetimeService()
    {
        return null;
    }
}

The code before the actual CreateApplicationHost call in the factory method requires an explanation: while the CreateApplicationHost is a convenient method, it’s hardwired to only search for assemblies in the bin folder relative to the physical path of the web project (or the GAC). Rick Strahl mentions this on his blog, and in fact if you check the framework reference sources, or inspect the assemblies with ILSPY you’ll discover a hardwired bin reference.

So for a quick fix, in the code above I just copy the relevant assemblies to that folder. A more elegant solution would be to do a bit more work and create a new AppDomain using AppDomain.CreateDomain(…) and tell it where to find assemblies yourself.

Next we want to create an instance of your web/MVC project’s main class, which is some class – in global.asax.cs – derived from HttpApplication, so we also need a method on AppHost which does this:

private HttpApplication _mvcApp;

private void HostMvcApp() where T: HttpApplication
{
    //usually IIS does this, but we need to serve static files ourselves
    HttpApplication.RegisterModule(typeof(StaticFileHandlerModule));

    _mvcApp = Activator.CreateInstance();
}

This will bootstrap your MVC application and call into it’s Application_Start method where you can register routes, areas, bundles, etc as usual.

Since the ASP.NET pipeline does not serve static files, we need to do this ourselves. I do this here by registering a StaticFileHandlerModule, which we’ll implement in a future post.

So now we have hosted the MVC application and set up a HTTP listener. The only thing that’s left is to connect these two together so the ASP.NET pipeline will handle these requests. I’ll discuss how you do this in the next blog.

Ruurd Keizer

Author Ruurd Keizer

Quantumphysics PhD disguised as software architect, developer, and cloud native platform greasemonkey. Analytic, pragmatic, result oriented, never forgetting the bottom line. Interested in the whole picture: from businessvalue down to the bare metal.

More posts by Ruurd Keizer
23 May 2013

Leave a Reply