An Inside Look at ASP.NET 5 Execution – Part III – DNX Application Host

On the previous post of this series I followed code execution on the CLR Native Host and on the Managed Entry Point up to the point where the application layer is invoked. In this case, that layer starts on the application host provided by the runtime (Microsoft.Framework.ApplicationHost assembly).

If we take a look on the entry point (Program class) we can see , once again, the use of dependency injection (DI).

public Program(
    IAssemblyLoaderContainer container,
    IApplicationEnvironment environment,
    IServiceProvider serviceProvider
){ ... }

As discussed on the previous post of this series, instance Main methods are now supported. Note that the types that this class depends on were registered on the DI infrastructure by the Managed Entry Point.

The first task of the Application Host is to extract/consume some common options from the command line. This is where things such as the packages directory and run configurations are parsed (1) resulting in a DefaultHostOptions instance that will parameterize the remaining execution. In addition, the next command line option is assumed as a tentative application name and the remaining arguments are returned (2).

var app = new CommandLineApplication(throwOnUnexpectedArg: false);
//...
var optionPackages = app.Option("--packages ", "Directory containing packages", CommandOptionType.SingleValue); (1)
var optionConfiguration = app.Option("--configuration ", "The configuration to run under", CommandOptionType.SingleValue);
//...
defaultHostOptions = new DefaultHostOptions();
defaultHostOptions.PackageDirectory = optionPackages.Value();
defaultHostOptions.Configuration = optionConfiguration.Value() ?? _environment.Configuration ?? "Debug";
//...
var remainingArgs = new List();
remainingArgs.AddRange(app.RemainingArguments);
if (remainingArgs.Any())
{
  defaultHostOptions.ApplicationName = remainingArgs[0]; (2)
  outArgs = remainingArgs.Skip(1).ToArray();
}
//...

At this point, if we’re running “dnx . web” on the command line, the application name is “web” (which is actually a command on the project) and there aren’t any remaining arguments.

The major responsibility of the Application Host is to add support for the new project structure (project.json). Following the execution, we can see that this logic is based on the DefaultHost class (1). If a project is not found on the application root directory, an exception is thrown. On the other hand, if a project is found, DNX tries to expand the project command (“web” in this case) to find the final application name and arguments (2).

var host = new DefaultHost(options, _serviceProvider); (1)
// ...
var lookupCommand = string.IsNullOrEmpty(options.ApplicationName) ? "run" : options.ApplicationName;
string replacementCommand;
if (host.Project.Commands.TryGetValue(lookupCommand, out replacementCommand)) (2)
{
    var replacementArgs = CommandGrammar.Process(replacementCommand, ...);
    options.ApplicationName = replacementArgs.First(); (3)
    programArgs = replacementArgs.Skip(1).Concat(programArgs).ToArray();
}

At this point, for our example, the application name becomes “Microsoft.AspNet.Hosting” (3), since the “web” command is defined as follows on the project.json file:

"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000"

The remaining arguments are the server type and endpoint which will be processed by that layer.

Part of the support for the new project system is the ability to load assemblies from project references and NuGet packages. To that end, new assembly loaders are added to the IAssemblyLoaderContainer. Note that this container was previously initialized by the Managed Entry Point; the Application Host simply adds support for other means of resolving assemblies.

host.AddLoaders(_container);

 

// Excerpt from AddLoaders 
var loaders = new[]
{
    typeof(ProjectAssemblyLoader),
    typeof(NuGetAssemblyLoader),
};
// ...
foreach (var loaderType in loaders)
{
    var loader = (IAssemblyLoader)ActivatorUtilities.CreateInstance(ServiceProvider, loaderType);
    container.AddLoader(loader) // ...
}

The last step is to invoke the entry point on the application assembly (the last layer). This is done by loading an assembly with the current application name and then using EntryPointExecutor, just like the Managed Entry Point did in order to invoke the Application Host.

 Assembly assembly = host.GetEntryPoint(options.ApplicationName);
 // ...
 return EntryPointExecutor.Execute(assembly, args, host.ServiceProvider);

At this point we finally get to ASP.NET code! I’ll continue from there on the following post.

As a final note: I didn’t go into the details of the DefaultHost class, which wraps the logic of handling the new project structure. Nevertheless, it worthy taking a look at the code, namely to the Initialize method and from there to the ApplicationHostContext class, which articulates the core logic of processing the project and resolving its dependencies. The ProjectResolver and Project classes are a must! 🙂

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s