ASP.NET Core middleware with Roslyn Analyzers - Part 1
Tags: dotnet, dotnetcore, csharp, aspnetcore
Published: 2021-08-25 10:41:41

---
title: ASP.NET Core middleware with Roslyn Analyzers - Part 1
published: true
description: Learn how to create Roslyn Analyzers to help add ASP.NET Core middleware and in the right order.
tags: dotnet, dotnetcore, csharp, aspnetcore
canonical_url: https://blog.elmah.io/asp-net-core-middleware-with-roslyn-analyzers-part-1/
---

ASP.NET Core middleware is a clever way to configure features like authentication, routing, and error logging in ASP.NET Core. Adding the different pieces of middleware and in the right order can be quite a nightmare, though. In this post, I'll show you how Roslyn Analyzers can help.

Middleware works like pearls on a string. The order you add your middleware determines which middleware is called first and which is called last. This can cause serious problems, like error logging middleware that is never notified of the errors happening. The problem is already nicely described in Andrew Lock's [Inserting middleware between UseRouting() and UseEndpoints() as a library author - Part 1](https://andrewlock.net/inserting-middleware-between-userouting-and-useendpoints-as-a-library-author-part-1/) and [Part 2](https://andrewlock.net/inserting-middleware-between-userouting-and-useendpoints-as-a-library-author-part-2/), so make sure to read through those if you have felt the pain in your projects already.

I wanted to create a couple of Roslyn Analyzers to help elmah.io users insert the call to `UseElmahIo` and in the right place. Analyzers are small pieces of C# code running inside Visual Studio that analyzes the project source code and report any problems to the Visual Studio *Error List* window.

To create new Roslyn Analyzers, the simplest approach is to base it on the template already available in Visual Studio. Create a new project and select the *Analyzer with Code Fix (.NET Standard)* template (install the *.NET Compiler Platform SDK* from the Visual Studio Installer if not already installed). This generates a new project with a *MakeConst* analyzer and code fix. You can delete the two C# files since we will create new ones for this demo.

Let's start by creating an analyzer that verifies that the `UseElmahIo` method is even added to the `Configure` method in the `Startup.cs` file. I'm creating a new C# class named `CallUseElmahIoAnalyzer.cs`. I've pretty much copied the basic structure from the `MakeConst` analyzer previously there:

```csharp
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CallUseElmahIoAnalyzer : DiagnosticAnalyzer
{
    public const string DiagnosticId = "EIO1001";

    private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
        DiagnosticId,
        "Configure must call UseElmahIo",
        "Configure must call UseElmahIo",
        "Elmah.Io.CSharp.AspNetCoreRules",
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

    public override void Initialize(AnalysisContext context)
    {
    }
}
```

There are a few things to notice. Analyzers declare and extend the `DiagnosticAnalyzer` class. This will make Visual Studio pick up your analyzer automatically once installed as a VSIX (Visual Studio extension). Metadata for the analyzer is declared as a `DiagnosticDescriptor` and includes an id, the message to show to the user, as well as a couple of other things. The `Initialize` method is where the magic happens.

Analyzers are implemented using a visitor pattern. You register handlers for code pieces you are interested in and Roslyn automatically parses the source code under analysis and provides your code with metadata. The code may look a bit weird and over-engineered at first, but turns out pretty strong once you get acquainted with the model.

For this first analyzer, we want to see if the `Configure` method calls the `UseElmahIo` method. For this simple example, we assume that the call should be made inside a method named `Configure` in a class named `Startup`:

```csharp
public override void Initialize(AnalysisContext context)
{
    context.RegisterCodeBlockStartAction<SyntaxKind>(cb =>
    {
        // We only care about method bodies.
        if (cb.OwningSymbol.Kind != SymbolKind.Method) return;
        var method = (IMethodSymbol)cb.OwningSymbol;
        // We only care about methods named ConfigureServices
        if (method.Name != "Configure") return;
        if (method.ContainingType.Name != "Startup") return;
    });
}
```

The action provided for the `RegisterCodeBlocStartAction` method is called by Roslyn every time a new code block begins. A code block can be a class, a field, a method, etc. By inspecting metadata, the example returns if the current callback doesn't fulfill the requirements already mentioned.

If the code hasn't returned after the last `if` statement, we know that we are currently inside the `Configure` method. Roslyn allows for nested syntax actions, why we can register a new callback:

```csharp
public override void Initialize(AnalysisContext context)
{
    context.RegisterCodeBlockStartAction<SyntaxKind>(cb =>
    {
        // the code from the previous example

        bool useElmahIoInvocationFound = false;

        cb.RegisterSyntaxNodeAction(ctx =>
        {
            var node = ctx.Node as InvocationExpressionSyntax;
            if (node == null) return;
            var expression = node.Expression as MemberAccessExpressionSyntax;
            if (expression == null) return;
            var methodName = expression.Name?.Identifier.ValueText;
            if (methodName == "UseElmahIo") useElmahIoInvocationFound = true;
        }, SyntaxKind.InvocationExpression);
    });
}
```

The new code registers a syntax node action to get a notification of each invocation expression inside the `Configure` method (by specifying `SyntaxKind.InvocationExpression` as a parameter to the `RegisterSyntaxNodeAction` method). On each syntax node, we check if it's a method call and if the method call is `UseElmahIo`. In case we find a call to `UseElmahIo` we set the `useElmahIoInvocationFound` boolean to true.

The last thing missing is to register an end action. When using the `RegisterCodeBlockStartAction` method, you need to provide a callback for Roslyn to call when on the end of the semantic analysis for the method analyzed (in this case `Configure`). Confused? Let's look at the code:

```csharp
public override void Initialize(AnalysisContext context)
{
    context.RegisterCodeBlockStartAction<SyntaxKind>(cb =>
    {
        // the code from the previous example

        cb.RegisterCodeBlockEndAction(ctx =>
        {
            if (!useElmahIoInvocationFound)
            {
                var diag = Diagnostic.Create(Rule, method.Locations[0]);
                ctx.ReportDiagnostic(diag);
            }
        });
    });
}
```

In case the `useElmahIoInvocationFound` boolean is still set to `false` we use the `ReportDiagnostic` method to show a warning inside Visual Studio. The metadata used for the warning is taken from the `Rule` field that we already discussed as well as the location of the `Configure` method.

Let's look at the entire `Initialize` method:

```csharp
public override void Initialize(AnalysisContext context)
{
    context.RegisterCodeBlockStartAction<SyntaxKind>(cb =>
    {
        // We only care about method bodies.
        if (cb.OwningSymbol.Kind != SymbolKind.Method) return;
        var method = (IMethodSymbol)cb.OwningSymbol;
        // We only care about methods named ConfigureServices
        if (method.Name != "Configure") return;
        if (method.ContainingType.Name != "Startup") return;

        bool useElmahIoInvocationFound = false;

        cb.RegisterSyntaxNodeAction(ctx =>
        {
            var node = ctx.Node as InvocationExpressionSyntax;
            if (node == null) return;
            var expression = node.Expression as MemberAccessExpressionSyntax;
            if (expression == null) return;
            var methodName = expression.Name?.Identifier.ValueText;
            if (methodName == "UseElmahIo") useElmahIoInvocationFound = true;
        }, SyntaxKind.InvocationExpression);

        cb.RegisterCodeBlockEndAction(ctx =>
        {
            if (!useElmahIoInvocationFound)
            {
                var diag = Diagnostic.Create(Rule, method.Locations[0]);
                ctx.ReportDiagnostic(diag);
            }
        });
    });
}
```

To test the new analyzer, make sure to set the VSIX project as the startup project and hit F5. This will start an experimental instance of Visual Studio. Create a new ASP.NET Core project or open an existing one and navigate to the `Startup` class. If no `UseElmahIo` call is made, you should see the warning directly in Visual Studio:

![](https://blog.elmah.io/content/images/2020/02/useelmahio-1-.png)

To distribute your new analyzer, either upload the generated VSIX file to the Visual Studio Marketplace or publish the project containing the analyzers as a NuGet package.

That's it! In the [next post](https://dev.to/thomasardal/asp-net-core-middleware-with-roslyn-analyzers-part-2-3625), I will continue the example by creating an analyzer that verifies the correct order of middleware. The full source code for the elmah.io Roslyn Analyzers for ASP.NET Core can be found here: [https://github.com/elmahio/Elmah.Io.AspNetCore.Analyzers](https://github.com/elmahio/Elmah.Io.AspNetCore.Analyzers)

## Would your users appreciate fewer errors?

elmah.io is the easy error logging and uptime monitoring service for .NET. Take back control of your errors with support for all .NET web and logging frameworks.

➡️ [Error Monitoring for .NET Web Applications](https://elmah.io/?utm_source=devto&utm_medium=social&utm_campaign=devtoposts) ⬅️

This article first appeared on the elmah.io blog at https://blog.elmah.io/asp-net-core-middleware-with-roslyn-analyzers-part-1/