Application Insight for C# WPF

Note See an updated Application Insight client post on my new blog

There is a lot of talk about Microsoft’s Application Insight platform and rightly so, it is an amazing tool for gathering and analyzing application telemetry with a minimal amount of work, but there is one catch, it is not officially available for WPF (or Forms if that floats your boat.) This guide will walk you through the steps required to successfully implement Application Insights in your WPF application.

The code that I’m going to be walking through is available on my GitHub page so feel free to go and fork yourself a copy or just follow along here. My code makes use of the awesome Fody library as well as the Property Changing library for Fody. I also use the Relay Command class which can be found here.

Setting up Application Insights in Azure

First things first you are going to need an Azure account. If you do not have an account you can sign up for a free trial here. Once you have your Azure account set up navigate to the Azure Portal

In the side panel choose Browse All -> Application Insights -> Add
Add.PNG

Fill in the name value and make sure to choose the Windows Store Application as you Application Type and click create.
NewApp.png

Our Azure setup for Application Insight is now almost complete, all that remains is to grab our Instrumentation Key and then it’s time to head to Visual Studio for some coding. To get your Instrumentation Key click on your newly created Application Insight item then choose Settings
Settings.png

After that click on Properties and your Instrumentation Key will be displayed.
InstrumentationKey.png

Setting up Application Insight in C#

In Visual Studio create or open your project then right click it in the Solution Explorer and choose Manage NuGet Packages…
NugetAdd.png

This will open the NuGet package manager. Type Application Insights into the search box, then choose Microsoft.ApplicationInsights.Web package (at time of writing the newest version was 1.1.0) Click install and if prompted allow the NuGet package manager to make the required changes to your project. You will also need to accept the Terms of Service and after that Application Insights will have been added to your project.
NugetGet.png

The code

Now we come to the actual fun part, the code to make our application work with Application Insights.

The example project has the following layout
Solution.PNG

The code is written to be used with Dependency Injection (as a singleton), if you do not want to use it that way another option will be to convert it to a singleton class or a static class.

The rest of this guide will primarily focus on the ApplicationInsightHelper class which stores our Telemetry client and relays all of the information back to it, in this way there is no need to keep passing the Session Key or User ID around your application.

ApplicationInsightHelper class

The first thing your application will need is a way to uniquely identify your users, for this example I am simply using the Windows username but in a production application you will need something else as this method has obvious shortcoming. A good idea is to use something like a username (if authentication is required and the username is unique) or a GUID that is created on first application run and then stored alongside the application.

To assist Application Insights in tracking user sessions we will also need a session key, the key can simply be created at startup and then stored until the application is shut down.

So, let’s take a look at our variables


private readonly TelemetryClient _telemetryClient;
private string _sessionKey;
private string _userName;
private string _osName;
private string _version;
private string _application;
private string _manufacturer;
private string _model;

Besides the two values discussed above we also want to store some additional information for tracking.
In this case we are also tracking the OS that the application is running on (more on this in a bit), the version of the application itself, the application name as well as the manufacturer and model the hardware is running on.

Below is the code used to retrieve the values from the runtime enviroment


private void GatherDetails()
{
_sessionKey = Guid.NewGuid().ToString();
_userName = Environment.UserName;
_osName = GetWindowsFriendlyName();
_version = $"v.{ Assembly.GetEntryAssembly().GetName().Version}";
_application = $"{ Assembly.GetEntryAssembly().GetName().Name} {_version}";
_manufacturer = (from x in new ManagementObjectSearcher("SELECT Manufacturer FROM Win32_ComputerSystem").Get().OfType() select x.GetPropertyValue("Manufacturer")).FirstOrDefault()?.ToString() ?? "Unknown";
_model = (from x in new ManagementObjectSearcher("SELECT Model FROM Win32_ComputerSystem").Get().OfType() select x.GetPropertyValue("Model")).FirstOrDefault()?.ToString() ?? "Unknown";
}

The code is fairly straight-forward, the only really strange part is the methods to retrieve the manufacturer and model from WMI.

Beside that there is also the code which retrieves the Windows name from WMI, the code has an extra condition that, should the Windows name retrieval fail it simply populates the OS version.


private string GetWindowsFriendlyName()
{
var name = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType() select x.GetPropertyValue("Caption")).FirstOrDefault();
return name?.ToString() ?? Environment.OSVersion.ToString();
}

Next we need to set up the Telemetry client so that this data will reach our server on each request


private void SetupTelemetry()
{
_telemetryClient.Context.Properties.Add("Application Version",
_version);
_telemetryClient.Context.User.Id = _userName;
_telemetryClient.Context.User.UserAgent = _application;
_telemetryClient.Context.Component.Version = _version;
_telemetryClient.Context.Session.Id = _sessionKey;
_telemetryClient.Context.Device.OemName = _manufacturer;
_telemetryClient.Context.Device.Model = _model;
_telemetryClient.Context.Device.OperatingSystem = _osName;
}

The TelemetryClient’s context is laid out in a very intuitive way, with the above items being of the most interest (although there are other things like Device.ScreenResolution). All of the items above are fairly self explanatory so I won’t dig into further details here.

To guarantee that the TelemetryClient is set up correctly with all of the details we need we call the methods we created above from the constructor. While initializing the TelemetryClient we will also pass it our Instrumentation Key.


public ApplicationInsightHelper()
{
_telemetryClient = new TelemetryClient() {InstrumentationKey = "{Your key here}" };
GatherDetails();
SetupTelemetry();
}

The code above will guarantee that our client is correctly set up for use upon initialization.

The final step is to add methods that allow our other classes to actually pass telemtry data to the client. Having these helper methods is an easy way to prevent callers from having direct access to our TelemtryClient preventing changes to TelemetryClient’s context data in sub-modules

The TrackPageView methods is used to track page view, in a WPF application this can be used to track screens displayed to the user


public void TrackPageView(string pageName)
{
_telemetryClient.TrackPageView(pageName);
}

Next up we have code to keep track of any handled exceptions that might occur in our code, while the exception is handled it is still of interest to keep track of them. For this demo all handled exceptions are simply tracked as Non-fatal Exception but in a production application it is probably worth keeping more spesific records (via an extra parameter)


public void TrackNonFatalExceptions(Exception ex)
{
var metrics = new Dictionary { { "Non-fatal Exception", 1 } };
_telemetryClient.TrackException(ex, null, metrics);
}

We have an additional method that we will use to keep track of app crashes. By default Application Insight considers all exceptions as handled and will not display application crashes correctly unless handled with the code below


public void TrackFatalException(Exception ex)
{
var exceptionTelemetry = new Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry(new
Exception());
exceptionTelemetry.HandledAt =
Microsoft.ApplicationInsights.DataContracts.ExceptionHandledAt.
Unhandled;
_telemetryClient.TrackException(exceptionTelemetry);
}

And finally we need code to be able to push our TelemtryClient data to the server


public void FlushData()
{
DoDataFlush();
}

App.xaml class

We need to wire our application up to allow us to flush the TelemetryClient on application shutdown as well as to keep track of application crashes. To do the head over to the App.xaml class. By default App.xaml contains a StartupUri value but we will remove this in favor of starting the application ourselves.

Next choose View Code to view the code for the App.xaml file. We will override the OnStartup method and use this method to start our ApplicationInsightHelper class (the one we coded above).


protected override void OnStartup(StartupEventArgs e)
{
try
{
_applicationInsightHelper = new ApplicationInsightHelper();
_context = new DataContext(_applicationInsightHelper);

//We use this to get access to unhandled exceptions so we can
//report app crashes to the Telemetry client
var currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += CurrentDomain_UnhandledException;
currentDomain.ProcessExit += CurrentDomain_ProcessExit;
var mainWindow = new MainWindow(_context,
_applicationInsightHelper);
mainWindow.Show();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}

The only thing special about the code aboce is the subscription to the CurrentDomain’s UnhandledException and ProcessExit events. These events will notify us when the application has encountered an unhandled exception or has exited respectively. The handlers for the evnets are as follows:

Application Exit


private void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
_applicationInsightHelper.FlushData();
}

Applicaiton Crash


private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
_applicationInsightHelper.TrackFatalException(e.ExceptionObject as Exception);
_applicationInsightHelper.FlushData();
}

Tracking telemetry data

Now to track page views or exceptions in your classes simply pass the ApplicationInsightHelper instance to them then call the appropriate method.

And that is it, with this our application is ready to track telemetry and send it back to our Azure account, where it can be viewed.

Viewing the data in Azure

The Application overview gives you a general overview of your application. You can dig futher into these metrics to see more details like pages per session and such.
Application Overview.PNG

Next is the Usage details which show some details about the kind of devices that use your app and from where your app is used
Usage.PNG

And finally we have the Diagnostics which gives more detail about exceptions and crashes as well as the ability to review the raw data
Diagnostics.PNG

And that is it, you have just successfully implemented Application Insights in a WPF app. Happy tracking!

Note: I apologize for the hideous code blocks, it’s a limitation of the platform and there is nothing I can do about it.

 
88
Kudos
 
88
Kudos

Now read this

Multi-value SSRS report parameters from C#

If you are calling SSRS reports from C# and setting the parameters programatically you will sooner or later come across a case where you have to assign a parameter with multiple values. This problem proved quite vexing but I have found... Continue →