How to use the Aspire Dashboard with a legacy WinForms application
Volkmar Rigo

Volkmar Rigo @volkmarr

Location:
Bruneck, Italy
Joined:
Jun 8, 2019

How to use the Aspire Dashboard with a legacy WinForms application

Publish Date: Aug 14
0 0

Introduction

If you have a legacy Windows Forms (WinForms) application and want to add modern observability, this post shows how to wire up OpenTelemetry tracing in .NET Framework and stream it to the .NET Aspire Dashboard — using only a lightweight Docker container and a few lines of code.

But why use Aspire Dashboard for legacy apps? Modern observability platforms can feel out of reach for older desktop applications. The .NET Aspire Dashboard gives you a clean UI to inspect traces locally, and OpenTelemetry provides a vendor-neutral way to capture telemetry from your code.

The main challanges are:

  • configure OpenTelemetry and the Aspire Dashboard to work with the .Net Framework 4.7.2
  • manually add traces because auto instrumentation is not available for desktop applications

I've created a proof of concept that shows how to solve both issues. It is available at https://github.com/VolkmarR/OTEL-Traces-Winforms-Aspire

Aspire Dashboard configuration

The repo contains a docker compose file with the necessary configuration for the Aspire Dashboard.

Since the GRPC Protocol is not supported by the .Net Framework 4.7.2, it is very important to add the "4318:18890" port mapping, used by the HttpProtobuf Protocol.

Application configuration

The OpenTelemetry.Exporter.OpenTelemetryProtocol nuget package must be added to the WinForms application.

This function needs to be added to the project and called in the program.cs. Make sure that the setup method is only executed for local development and not for production.

// OTEL_Traces/OpenTelemetry/Tracing.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

public static class Tracing
{
    public static ActivitySource TelemetrySource;

    // Initializes OpenTelemetry and sends data to Aspire.
    public static TracerProvider Setup(string sourceName)
    {
        TelemetrySource = new ActivitySource(sourceName);

        return Sdk.CreateTracerProviderBuilder()
            .AddSource(sourceName)
            .AddOtlpExporter(o =>
            {
                o.Protocol = OtlpExportProtocol.HttpProtobuf;
                o.Endpoint = new Uri("http://localhost:4318/v1/traces");
            })
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
                Application.ProductName.Replace(' ', '-'),
                serviceVersion: "1.0",
                autoGenerateServiceInstanceId: false))
            .Build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Instrumentation

Since there is no automatic instrumentation, the spans for the traces must be added manually to the code.

There are different ways to approach this. My recommendation is to find methods in your base classes that perform noteworthy tasks that take a certain amount of time. Executing SQL statements or making HTTP calls are good examples.

Here is the example from the repo, simulating an SQL execution

public void Execute(string sql)
{
    Activity activity = null;
    try
    {
        // Only start a child activity when there is a parent.
        if (Activity.Current != null)
            activity = Tracing.TelemetrySource.StartActivity();

        // add custom information to the span
        activity?.SetTag("custom.sql", sql);

        // ...

    }
    finally
    {
        activity?.Dispose();
    }
}
Enter fullscreen mode Exit fullscreen mode

This way, spans are only created when a root activity was created before.

These root activities can be started in methods that are triggered by the user (button clicks, ...).

Here's the example for that.

private void LoadButton_Click(object sender, EventArgs e)
{
    // ...

    using (var _ = Tracing.TelemetrySource.StartActivity())
    {
        for (var i = 0; i < userCount; i++)
            query.Execute($"Select * from Users where Id = {i}");
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Summary

This article contains the key steps to use OpenTelemetry and the Aspire Dashboard in a legacy Winforms application. The github repo mentioned in the article contains a working prototype to show how this works in practice.

Comments 0 total

    Add comment