Gergely Kalapos


How is Application Insights loaded into an ASP.NET Core 2.0 application and how you can do the same

Posted on June 06, 2017



Intro

The sample code is here on GitHub. The AwesomeHostingStartupLib is the library that we want to inject. The AspNetCoreApplication_InjectionTarget is just a plain ASP.NET Core application serving as the target.

In ASP.NET Core 1.x Application Insights (AI) was basically a library, so you added it to your application and then you initialized it with a few lines like this:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup()
        .UseApplicationInsights() //<--- here! 
        .Build();

    host.Run();
}

This changed in ASP.NET Core 2.0: you basically don't have to do anything: the application does not have a dependency on Application Insights, still you have the usual AI telemetry in Visual Studio when you debug and you can turn on AI on Azure with a single switch without redeploying the application.

In this post, I will show how this works and we will implement our own stuff to do something similar.

In ASP.NET Core 2.0 there are basically 3 things, which make this possible:

  • With ASP.NET Core 2.0 there is a new interface called IHostingStartup. It has only 1 method, and you can basically influence the process of building the web host
  • Then the DOTNET_ADDITIONAL_DEPS environment variable points the dotnet host to an additional .deps.json file. This .deps.json file will be merged with the application's and the shared framework's deps file. This is basically how the CoreCLR finds all the dlls necessary to run the application (...plus with DOTNET_ADDITIONAL_DEPS also some additional dlls)
  • The ASPNETCORE_HOSTINGSTARTUPASSEMBLIES environment variable, which points to an assembly with a class that implements IHostingStartup.

With this setup you can basically inject a DLL into an ASP.NET Core application. Again: in this scenario, the application has zero knowledge about the DLL we load with this setup.

Implementing IHostingStartup

The Interface is defined in the Microsoft.AspNetCore.Hosting.Abstractions package, so first let's reference it:

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" 
            Version="2.0.0-preview1-final" />
    <PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" 
            Version="2.0.0-preview1-final" />
</ItemGroup>
The second dependency (Microsoft.Extensions.DiagnosticAdapter) is needed, because we will use DiagnosticSource in the sample. Since the deps.json file will be generated based on this .csproj file it is important that the versions match. I reference preview1-final and that is what I have on my system, so when the .deps file will be merged it will match with the application's dependencies.

Now let's implement the interface:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using AwesomeHostingStartupLib;
using Microsoft.AspNetCore.Hosting;

[assembly: HostingStartup(typeof(AwesomeHostingStartup))]

namespace AwesomeHostingStartupLib
{
    internal class Program { public static void Main() { } }

    public class AwesomeHostingStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            Console.WriteLine("================MY_AWESOME_HOSTINGSTARTUP_LOADED==============");

            var _diagnosticsListenersSubscription = 
                DiagnosticListener.AllListeners.Subscribe(new DummyObserver((listener)
                => {              
                    var _handledExceptionSubscription =
                       listener.Subscribe(new DummyObserver>((diagnosticEvent) => {
                          Console.WriteLine($"DiagnosticSource Event: {diagnosticEvent.Key}");
                    }));
            }));
        }

        private class DummyObserver : IObserver
        {
            public DummyObserver(Action callback) { _callback = callback; }
            public void OnCompleted() { }
            public void OnError(Exception error) { }
            public void OnNext(T value) { _callback(value); }

            private Action _callback;
        }
    }
}

This IHostingStartup implementation does 2 things: 1) Prints the text when it is loaded (good for demonstrating that it is really loaded) 2) prints the key of every DiagnosticSource event the application sends. This is obviously just a dummy implementation, I guess you can imagine what you can do here. Btw. there is no way to mess around with the Middleware setup in this stage! (see Resources below - ASP.NET Community Standup).

A few other points:

  • The [assembly: HostingStartup(typeof(AwesomeHostingStartup))] tells which class implements the IHostingStartup. If you add this, it always works... There was some discussion about auto discovery when the IHostingStartup is contained in the application. Well, that did not work for me, maybe I did something wrong... but I guess when you do this via environment variables you need that always.
  • As you see there is a main method: the reason for that is that Microsoft.AspNetCore.Hosting.Abstractions can only be referenced from a console application, not from a library. Again, no idea why, I just accepted it. Btw. the Azure and Application Insights integration stuff also has a main method, so I was fine with that.

DOTNET_ADDITIONAL_DEPS and ASPNETCORE_HOSTINGSTARTUPASSEMBLIES

Now I followed the setup used by Application Insights. When you start the ASP.NET Core app from VS and look into it with Process Explorer you see a few interesting things:

So:

  • DOTNET_ADDITIONAL_DEPS C:\Program Files\dotnet\additionalDeps\Microsoft.AspNetCore.ApplicationInsights.HostingStartup
  • ASPNETCORE_HOSTINGSTARTUPASSEMBLIES Microsoft.AspNetCore.ApplicationInsights.HostingStartup
  • And then the dll, which is loaded because of these settings is this: C:\Program Files\dotnet\store\x64\netcoreapp2.0\microsoft.aspnetcore.applicationinsights.hostingstartup\2.0.0-preview1-final\lib\netcoreapp2.0\Microsoft.AspNetCore.ApplicationInsights.HostingStartup.dll
So let's replicate this! First let's build the AwesomeHostingStartupLib project. Then grab the AwesomeHostingStartupLib.dll and copy it under C:\Program Files\dotnet\store\x64\netcoreapp2.0\AwesomeHostingStartupLib\1.0.0\lib\netcoreapp2.0.

Then we have to adapt the deps.json file. I did not figure out how to generate deps.json files that do not reference the dll which it belongs to from the same folder, so I modified the file manually, which is sure terrible, so if someone reading this knows how to do this properly then please comment below.

So let's search for this part in the AwesomeHostingStartupLib.deps.json file:
"runtime": {
    "AwesomeHostingStartupLib.dll": {}
}     
and change it to
"runtime": {
    "lib/netcoreapp2.0/AwesomeHostingStartupLib.dll": {}
}    
Then copy the modified AwesomeHostingStartupLib.deps.json file to C:\Program Files\dotnet\additionalDeps\AwesomeHostingStartupLib\shared\Microsoft.NETCore.App\2.0.0-preview1-002111-00. Now you have the files in place. You basically can set the environment variables and then EVERY application that get these settings will automatically load our AwesomeHostingStartupLib.

Let's try this out! Go to the AspNetCoreApplication_InjectionTarget project from the sample repo and set the environment variables:

set DOTNET_ADDITIONAL_DEPS=C:\Program Files\dotnet\additionalDeps\AwesomeHostingStartupLib
set ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=AwesomeHostingStartupLib

And then run it with dotnet run.

Here is what you should see:


As you see our AwesomeHostingStartupLib was loaded by the application. Again, the point here is that the app itself has no idea about AwesomeHostingStartupLib!

Final thoughts

According to my knowledge currently Azure, the IIS integration, and Application Insights already take advantage of this feature (see Azure integration repo below).

Now this stuff is super undocumented and I'm sure there is a better way to reference IHostingStartup implementation than the way I did it by just copying the files. I tried to store everything on a custom location and it did not work, the application just did not pick up my IHostingSturtup library, so I ended up replicating the setup of AI. If you know about a "proper way" of doing this feel free to comment below.

Resources


;