Adding an endpoint graph to your ASP.NET Core application: Visualizing ASP.NET Core 3.0 endpoints using GraphvizOnline - Part 2
Tags: aspnetcore, routing
Published: 2020-07-26 17:28:28

---
title: Adding an endpoint graph to your ASP.NET Core application: Visualizing ASP.NET Core 3.0 endpoints using GraphvizOnline - Part 2
published: true
date: 2020-07-07 10:00:00 UTC
tags: ASPNETCore,Routing
canonical_url: https://andrewlock.net/adding-an-endpoint-graph-to-your-aspnetcore-application/
---

 ![Adding an endpoint graph to your ASP.NET Core application](https://andrewlock.net/content/images/2020/graphs_2_banner.png)

In this post I show how to visualize the endpoint routes in your ASP.NET Core 3.0 application using the `DfaGraphWriter` service. I show how to generate a directed graph ([as shown in my previous post](https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/)) which can be visualized using [GraphVizOnline](https://dreampuf.github.io/GraphvizOnline/). Finally, I describe the points in your application's lifetime where you can retrieve the graph data.

In this post I only show how to create the "default" style of graph. In my next post I create a custom writer for generating the customised graphs like the one in [my previous post](https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/#understanding-the-different-types-of-node-).

## Using DfaGraphWriter to visualize your endpoints.

ASP.NET Core comes [with a handy class, `DfaGraphWriter`](https://github.com/dotnet/aspnetcore/blob/36856ca8f9932b88757bbf24904bf36868b4d727/src/Http/Routing/src/Internal/DfaGraphWriter.cs), that can be used to visualize your routes in an ASP.NET Core 3.x application:

```csharp
public class DfaGraphWriter
{
    public void Write(EndpointDataSource dataSource, TextWriter writer);
}
```

This class has a single method, `Write`. The `EndpointDataSource` contains the collection of `Endpoint`s describing your application, and the `TextWriter` is used to write the DOT language graph ([as you saw in my previous post](https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/)).

For now we'll create a middleware that uses the `DfaGraphWriter` to write the graph as an HTTP response. You can inject both the `DfaGraphWriter` and the `EndpointDataSource` it uses into the constructor using DI:

```csharp
public class GraphEndpointMiddleware
{
    // inject required services using DI
    private readonly DfaGraphWriter _graphWriter;
    private readonly EndpointDataSource _endpointData;

    public GraphEndpointMiddleware(
        RequestDelegate next, 
        DfaGraphWriter graphWriter, 
        EndpointDataSource endpointData)
    {
        _graphWriter = graphWriter;
        _endpointData = endpointData;
    }

    public async Task Invoke(HttpContext context)
    {
        // set the response
        context.Response.StatusCode = 200;
        context.Response.ContentType = "text/plain";

        // Write the response into memory
        await using (var sw = new StringWriter())
        {
            // Write the graph
            _graphWriter.Write(_endpointData, sw);
            var graph = sw.ToString();

            // Write the graph to the response
            await context.Response.WriteAsync(graph);
        }
    }
}
```

This middleware is pretty simple—we inject the necessary services into the middleware using dependency injection. Writing the graph to the response is a bit more convoluted: you have to write the response in-memory to a `StringWriter`, convert it to a `string`, and _then_ write it to the graph.

This is all necessary because the `DfaGraphWriter` writes to the `TextWriter` using _synchronous_ `Stream` API calls, like `Write`, instead of `WriteAsync`. Ideally, we would be able to do something like this:

```csharp
// Create a stream writer that wraps the body
await using (var sw = new StreamWriter(context.Response.Body))
{
    // write asynchronously to the stream
    await _graphWriter.WriteAsync(_endpointData, sw);
}
```

If `DfaGraphWriter` used asynchronous APIs, then you could write directly to `Response.Body` as shown above and avoid the in-memory `string`. Unfortunately, it's synchronous, and [you shouldn't write to `Response.Body` using synchronous calls for performance reasons](https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#avoid-synchronous-read-or-write-on-httprequesthttpresponse-body). If you _try_ to use pattern above then you _may_ get an `InvalidOperationException` like the following, depending on the size of the graph being written:

```
System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
```

> You might **not** get this exception if the graph is small, but you can see it if you try and map a medium-side application, such as the default Razor Pages app with Identity.

Let's get back on track—we now have a graph-generating middleware, so lets add it to the pipeline. There's two options here:

- Add it as an endpoint using endpoint routing.
- Add it as a simple "branch" from your middleware pipeline.

The former approach is the generally recommended method for adding endpoints to ASP.NET Core 3.0 apps, so lets start there.

### Adding the graph visualizer as an endpoint

To simplify the endpoint registration code, I'll create a simple extension method for adding the `GraphEndpointMiddleware` as an endpoint:

```csharp
public static class GraphEndpointMiddlewareExtensions
{
    public static IEndpointConventionBuilder MapGraphVisualisation(
        this IEndpointRouteBuilder endpoints, string pattern)
    {
        var pipeline = endpoints
            .CreateApplicationBuilder()
            .UseMiddleware<GraphEndpointMiddleware>()
            .Build();

        return endpoints.Map(pattern, pipeline).WithDisplayName("Endpoint Graph");
    }
}
```

We can then add the graph endpoint to our ASP.NET Core application by calling `MapGraphVisualisation("/graph")` in the `UseEndpoints()` method in `Startup.Configure()`:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz");
        endpoints.MapControllers();
        // Add the graph endpoint
        endpoints.MapGraphVisualisation("/graph");
    });
}
```

That's all we need to do. The `DfaGraphWriter` is already available in DI, so there's no additional configuration required there. Navigating to `http://localhost:5000/graph` (for example) generates the graph of our endpoints as plain text (shown here for the app from ](/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/):

```javascript
digraph DFA {
    0 [label="/graph/"]
    1 [label="/healthz/"]
    2 [label="/api/Values/{...}/ HTTP: GET"]
    3 [label="/api/Values/{...}/ HTTP: PUT"]
    4 [label="/api/Values/{...}/ HTTP: DELETE"]
    5 [label="/api/Values/{...}/ HTTP: *"]
    6 -> 2 [label="HTTP: GET"]
    6 -> 3 [label="HTTP: PUT"]
    6 -> 4 [label="HTTP: DELETE"]
    6 -> 5 [label="HTTP: *"]
    6 [label="/api/Values/{...}/"]
    7 [label="/api/Values/ HTTP: GET"]
    8 [label="/api/Values/ HTTP: POST"]
    9 [label="/api/Values/ HTTP: *"]
    10 -> 6 [label="/*"]
    10 -> 7 [label="HTTP: GET"]
    10 -> 8 [label="HTTP: POST"]
    10 -> 9 [label="HTTP: *"]
    10 [label="/api/Values/"]
    11 -> 10 [label="/Values"]
    11 [label="/api/"]
    12 -> 0 [label="/graph"]
    12 -> 1 [label="/healthz"]
    12 -> 11 [label="/api"]
    12 [label="/"]
}
```

Which can be visualized using GraphVizOnline:

![A ValuesController endpoint routing application](https://andrewlock.net/content/images/2020/graphs_2_01.png)

Exposing the graph as an endpoint in the endpoint routing system has both pros and cons:

- You can easily add authorization to the endpoint. You probably don't want just anyone to be able to view this data!
- The graph endpoint shows up as an endpoint in the system. That's obviously correct, but could be annoying.

If that final point is a deal breaker for you, you _could_ use the old-school way of creating endpoints, using branching middleware.

### Adding the graph visualizer as a middleware branch

Adding branches to your middleware pipeline was one of the easiest way to create "endpoints" before we had endpoint routing. It's still available in ASP.NET Core 3.0, it's just far more basic than the endpoint routing system, and doesn't provide the ability to easily add authorization or advanced routing.

To create a middleware branch, use the `Map()` command. For example, you could add a branch using the following:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // add the graph endpoint as a branch of the pipeline
    app.Map("/graph", branch => 
        branch.UseMiddleware<GraphEndpointMiddleware>());

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz");
        endpoints.MapControllers();
    });
}
```

The pros and cons of using this approach are essentially the opposite of the endpoint-routing version: there's no `/graph` endpoint in your graph, but you can't easily apply authorization to the endpoint!

![A ValuesController endpoint routing application without the graph endpoint](https://andrewlock.net/content/images/2020/graph_06.png)

For me, it doesn't make a _lot_ of sense to expose the graph of your application like this. In the next section, I show how you can generate the graph from a small integration test instead.

## Generating an endpoint graph from an integration test

[ASP.NET Core has a good story for running in-memory integration tests](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests), exercising your full middleware pipeline and API controllers/Razor Pages, _without_ having to make network calls.

As well as the traditional "end-to-end" integration tests that you can use to confirm the overall correct operation of your app, I sometimes like to write "sanity-check" tests, that confirm that an application is configured correctly. You can achieve this by using [the `WebApplicationFactory<>` facility](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1#basic-tests-with-the-default-webapplicationfactory), in _Microsoft.AspNetCore.Mvc.Testing_ which exposes the underlying DI container. This allows you to run code in the DI context of the application, but from a unit test.

To try it out

- Create a new xUnit project (my testing framework of choice) using VS or by running `dotnet new xunit`
- Install _Microsoft.AspNetCore.Mvc.Testing_ by running `dotnet add package Microsoft.AspNetCore.Mvc.Testing`
- Update the `<Project>` element of the test project to `<Project Sdk="Microsoft.NET.Sdk.Web">`
- Reference your ASP.NET Core project from the test project

We can now create a simple test that generates the endpoint graph, and writes it to the test output. In the example below, I'm using the default `WebApplicationFactory<>` as a class fixture; if you need to customise the factory, see [the docs](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests#customize-webapplicationfactory) or [my previous post](https://andrewlock.net/converting-integration-tests-to-net-core-3/) for details.

In addition to `WebApplicationFactory<>`, I'm also injecting `ITestOutputHelper`. You need to use this class to record test output with xUnit. [Writing directly to `Console` won't work.](https://xunit.net/docs/capturing-output).

```csharp
public class GenerateGraphTest
    : IClassFixture<WebApplicationFactory<ApiRoutes.Startup>>
{

    // Inject the factory and the output helper
    private readonly WebApplicationFactory<ApiRoutes.Startup> _factory;
    private readonly ITestOutputHelper _output;

    public GenerateGraphTest(
        WebApplicationFactory<Startup> factory, ITestOutputHelper output)
    {
        _factory = factory;
        _output = output;
    }

    [Fact]
    public void GenerateGraph()
    {
        // fetch the required services from the root container of the app
        var graphWriter = _factory.Services.GetRequiredService<DfaGraphWriter>();
        var endpointData = _factory.Services.GetRequiredService<EndpointDataSource>();

        // build the graph as before
        using (var sw = new StringWriter())
        {
            graphWriter.Write(endpointData, sw);
            var graph = sw.ToString();

            // write the graph to the test output
            _output.WriteLine(graph);
        }
    }
}
```

The bulk of the test is the same as for the middleware, but instead of writing to the response, we write to xUnit's `ITestOutputHelper`. This records the output against the test. In Visual Studio, you can view this output by opening Test Explorer, navigating to the `GenerateGraph` test, and clicking "Open additional output for this result", which opens the result as a tab:

![Viewing endpoint data from a test in Visual Studio](https://andrewlock.net/content/images/2020/graphs_2_02.png)

I find a simple test like this is often sufficient for my purposes. There's lots of advantages in my eyes:

- It doesn't expose this data as an endpoint
- Has no impact on your application
- Can be easily generated

Nevertheless, maybe you want to generate this graph from your application, but you don't want to include it using either of the middleware approaches shown so far. If so, just be careful exactly where you do it.

## You can't generate the graph in an IHostedService

Generally speaking, you can access the `DfaGraphWriter` and `EndpointDataSource` services from anywhere in your app that uses dependency injection, or that has access to an `IServiceProvider` instance. That means generating the graph in the context of a request, for example from an MVC controller or Razor Page is easy, and identical to the approach you've seen so far.

Where you must be careful is if you're trying to generate the graph _early_ in your application's lifecycle. This applies particularly to `IHostedService`s.

In ASP.NET Core 3.0, the web infrastructure was rebuilt on top of the generic host, which means your server (Kestrel) runs as an `IHostedService` in your application. In most cases, this shouldn't have a big impact, but it changes the _order_ that your application is built, compared to ASP.NET Core 2.x.

In ASP.NET Core 2.x, the following things would happen:

- The middleware pipeline is built.
- The server (Kestrel) starts listening for requests.
- The `IHostedService` implementations are started.

Instead, on ASP.NET Core 3.x, you have the following:

- The `IHostedService` implementations are started.
- The `GenericWebHostService` is started:
  - The middleware pipeline is built
  - The server (Kestrel) starts listening for requests.

The important thing to note is that the middleware pipeline isn't built until _after_ your `IHostedService`s are executed. As `UseEndpoints()` has not yet been called, `EndpointDataSource` won't contain any data!

> The `EndpointDataSource` will be empty if you try and generate your graph using `DfaGraphWriter` from an `IHostedService`.

The same goes if you try and use another standard mechanisms for injecting early behaviour, like `IStartupFilter`—these execute _before_ `Startup.Configure()` is called, so the `EndpointDataSource` will be empty.

Similarly, you can't just build a `Host` by calling `IHostBuilder.Build()` in `Program.Main`, and access the services using `IHost.Services`: until you call `IHost.Run`, and the server has started, your endpoint list will be empty!

These limitations may or may not be an issue depending on what you're trying to achieve. For me, the unit test approach solves most of my issues.

Whichever approach you use, you're stuck only being able to generate the "default" endpoint graphs shown in this post. As I mentioned [in my previous post](https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/), this hides a lot of really useful information, like which nodes generate an endpoint. In the next post, I show how to create a custom graph writer, so you can generate your own graphs.

## Summary

In this post I showed how to use the `DfaGraphWriter` and `EndpointDataSource` to create a graph of all the endpoints in your application. I showed how to create a middleware endpoint to expose this data, and how to use this middleware both with a branching middleware strategy and as an endpoint route.

I also showed how to use a simple integration test to generate the graph data without having to run your application. This avoids exposing (the potentially sensitive) endpoint graph publicly, while still allowing easy access to the data.

Finally, I discussed _when_ you can generate the graph in your application's lifecycle. The `EndpointDataSource` is not populated until after the `Server` (Kestrel) has started, so you're primarily limited to accessing the data in a Request context. `IHostedService` and `IStartupFilter` execute too early to access the data, and `IHostBuilder.Build()` only builds the DI container, not the middleware pipeline.