<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Lock "Sock"</title>
    <description>The latest articles on DEV Community by Andrew Lock "Sock" (@andrewlocknet).</description>
    <link>https://dev.to/andrewlocknet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F205409%2F22d5ee20-3e03-43c3-bde4-e598ab27e20c.jpg</url>
      <title>DEV Community: Andrew Lock "Sock"</title>
      <link>https://dev.to/andrewlocknet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrewlocknet"/>
    <language>en</language>
    <item>
      <title>Exploring the code behind IHttpClientFactory in depth</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 04 Aug 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/exploring-the-code-behind-ihttpclientfactory-in-depth-4p7g</link>
      <guid>https://dev.to/andrewlocknet/exploring-the-code-behind-ihttpclientfactory-in-depth-4p7g</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mzIfzwKg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mzIfzwKg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_banner.png" alt="Exploring the code behind IHttpClientFactory in depth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post I take a look at the code in the default implementation of &lt;code&gt;IHttpClientFactory&lt;/code&gt; in ASP.NET Core—&lt;code&gt;DefaultHttpClientFactory&lt;/code&gt;. We'll see how it ensures that &lt;code&gt;HttpClient&lt;/code&gt; instances created with the factory &lt;a href="https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/"&gt;prevent socket exhaustion&lt;/a&gt;, while also ensuring that &lt;a href="http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html"&gt;DNS changes are respected&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post assumes you already have a general idea of &lt;code&gt;IHttpClientFactory&lt;/code&gt; and what it's used for, so if it's new to you, take a look at &lt;a href="https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore"&gt;Steve Gordon's series on IHttpClientFactory&lt;/a&gt;, or &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests"&gt;see the docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code in this post is based on &lt;a href="https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs"&gt;the HEAD of the master branch&lt;/a&gt; of &lt;a href="https://github.com/dotnet/runtime"&gt;https://github.com/dotnet/runtime&lt;/a&gt; at the time of writing, after .NET 5 preview 7. But the code hasn't changed significantly for 2 years, so I don't expect it to change much soon!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've taken a couple of liberties with the code by removing null checks, in-lining some trivial methods, and removing some code that's tangential to this discussion. For the original code, &lt;a href="https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs"&gt;see GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A brief overview of IHttpClientFactory
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IHttpClientFactory&lt;/code&gt; allows you to create &lt;code&gt;HttpClient&lt;/code&gt; instances for interacting with HTTP APIs, using best practices to avoid common issues. Before &lt;code&gt;IHttpClientFactory&lt;/code&gt;, it was common to fall into one of two traps when creating &lt;code&gt;HttpClient&lt;/code&gt; instances:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Create and dispose of new &lt;code&gt;HttpClient&lt;/code&gt; instances as required&lt;/em&gt;. &lt;a href="https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/"&gt;This can lead to socket exhaustion&lt;/a&gt; due to the TIME_WAIT period required after closing a connection.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Create a singleton &lt;code&gt;HttpClient&lt;/code&gt; for the lifetime of the application&lt;/em&gt;. This can lead to &lt;a href="http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html"&gt;issues when the DNS record for the HTTP API you're using changes&lt;/a&gt;—the changes will not be respected by the &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;IHttpClientFactory&lt;/code&gt; was added in .NET Core 2.1, and solves this issue by separating the management of &lt;code&gt;HttpClient&lt;/code&gt;, from the the &lt;code&gt;HttpMessageHandler&lt;/code&gt; chain that is used to send the message. In reality, it is the lifetime of the &lt;code&gt;HttpClientHandler&lt;/code&gt; at the &lt;em&gt;end&lt;/em&gt; of the pipeline that is the important thing to manage, as this is the handler that actually makes the connection&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TPJWLbiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TPJWLbiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_01.png" alt="HttpClient and HttpClientHandler pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to simply managing the handler lifetimes, &lt;code&gt;IHttpClientFactory&lt;/code&gt; also makes it easy to customise the generated &lt;code&gt;HttpClient&lt;/code&gt; and message handler pipeline using an &lt;code&gt;IHttpClientBuilder&lt;/code&gt;. This allows you to "pre-configure" a named of typed &lt;code&gt;HttpClient&lt;/code&gt; that is created using the factory, for example to set the base address or add default headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.github.com/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/vnd.github.v3+json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"HttpClientFactory-Sample"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can add multiple configuration functions at the &lt;code&gt;HttpClient&lt;/code&gt; level, but you can &lt;em&gt;also&lt;/em&gt; add additional &lt;code&gt;HttpMessageHandler&lt;/code&gt;s to the pipeline. Steve shows how you can create your own handlers in &lt;a href="https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers"&gt;in his series&lt;/a&gt;. To add message handlers to a named client, use &lt;code&gt;IHttpClientBuilder.AddHttpMessageHandler&amp;lt;&amp;gt;&lt;/code&gt;, and register the handler with the DI container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.github.com/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimingHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// This handler is on the outside and executes first on the way out and last on the way in.&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidateHeaderHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This handler is on the inside, closest to the request.&lt;/span&gt;

    &lt;span class="c1"&gt;// Add the handlers to the service collection&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimingHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidateHeaderHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you call &lt;code&gt;ConfigureHttpClient()&lt;/code&gt; or &lt;code&gt;AddHttpMessageHandler()&lt;/code&gt; to configure your HttpClient, you're actually adding configuration messages to a named &lt;code&gt;IOptions&lt;/code&gt; instance, &lt;code&gt;HttpClientFactoryOptions&lt;/code&gt;. &lt;a href="https://andrewlock.net/using-multiple-instances-of-strongly-typed-settings-with-named-options-in-net-core-2-x/"&gt;You can read more about &lt;em&gt;named options&lt;/em&gt; here&lt;/a&gt;, but the details aren't too important for this post.&lt;/p&gt;

&lt;p&gt;That handles &lt;em&gt;configuring&lt;/em&gt; the &lt;code&gt;IHttpClientFactory&lt;/code&gt;. To use the factory, and create an &lt;code&gt;HttpClient&lt;/code&gt;, you first obtain an instance of the singleton &lt;code&gt;IHttpClientFactory&lt;/code&gt;, and then you call &lt;code&gt;CreateClient(name)&lt;/code&gt;, providing the name of the client to create.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't provide a name to &lt;code&gt;CreateClient()&lt;/code&gt;, the factory will use the default name, &lt;code&gt;""&lt;/code&gt; (the empty string).&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// IHttpClientFactory is a singleton, so can be injected everywhere&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DoSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Get an instance of the typed client&lt;/span&gt;
        &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Use the client...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The remainder of this post focus on what happens behind the scenes when you call &lt;code&gt;CreateClient()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an HttpClient and HttpMessageHandler
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CreateClient()&lt;/code&gt; method, shown below, is how you typically interact with the &lt;code&gt;IHttpClientFactory&lt;/code&gt;. I discuss this method below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Injected in constructor&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOptionsMonitor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpClientFactoryOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_optionsMonitor&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HttpMessageHandler&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disposeHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;HttpClientFactoryOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_optionsMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClientActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClientActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is a relatively simple method on the face of it. We start by creating the &lt;code&gt;HttpMessageHandler&lt;/code&gt; pipeline by calling &lt;code&gt;CreateHandler(name)&lt;/code&gt;, and passing in the name of the client to create. We'll look into that method shortly, as it's where most of the magic happens around handler pooling and lifetimes.&lt;/p&gt;

&lt;p&gt;Once you have a handler, a new instance of &lt;code&gt;HttpClient&lt;/code&gt; is created and passed to the handler. The important thing to note is the &lt;code&gt;disposeHandler: false&lt;/code&gt; argument. This ensures that disposing the &lt;code&gt;HttpClient&lt;/code&gt; &lt;em&gt;doesn't&lt;/em&gt; dispose the handler pipeline, as the &lt;code&gt;IHttpClientFactory&lt;/code&gt; will handle that itself.&lt;/p&gt;

&lt;p&gt;Finally, the latest &lt;code&gt;HttpClientFactoryOptions&lt;/code&gt; for the named client are fetched from the &lt;code&gt;IOptionsMonitor&lt;/code&gt; instance. This contains the configuration functions for the &lt;code&gt;HttpClient&lt;/code&gt; that were added in &lt;code&gt;Startup.ConfigureServices()&lt;/code&gt;, and sets things like the &lt;code&gt;BaseAddress&lt;/code&gt; and default headers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://andrewlock.net/creating-singleton-named-options-with-ioptionsmonitor/"&gt;I discussed using &lt;code&gt;IOptionsMonitor&lt;/code&gt; in a previous post&lt;/a&gt;. It is useful when you want to load named options in a singleton context, where you can't use the simpler &lt;code&gt;IOptionsSnapshot&lt;/code&gt; interface. It also has other change-detection capabilities that aren't used in this case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, the &lt;code&gt;HttpClient&lt;/code&gt; is returned to the caller. Let's look at the &lt;code&gt;CreateHandler()&lt;/code&gt; method now and see how the &lt;code&gt;HttpMessageHandler&lt;/code&gt; pipeline is created. There's quite a few layers to get through, so we'll walk through it step-by-step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Created in the constructor&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_activeHandlers&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;

&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_entryFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreateHandlerEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;LazyThreadSafetyMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecutionAndPublication&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;HttpMessageHandler&lt;/span&gt; &lt;span class="nf"&gt;CreateHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_activeHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_entryFactory&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartExpiryTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_expiryCallback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CreateHandler()&lt;/code&gt; method does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gets or creates an &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It stars a timer on the entry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;_activeHandlers&lt;/code&gt; field is a &lt;code&gt;ConcurrentDictionary&amp;lt;&amp;gt;&lt;/code&gt;, keyed on the name of the client (e.g. &lt;code&gt;"gitHub"&lt;/code&gt;). The dictionary values are &lt;code&gt;Lazy&amp;lt;ActiveHandlerTrackingEntry&amp;gt;&lt;/code&gt;. Using &lt;code&gt;Lazy&amp;lt;&amp;gt;&lt;/code&gt; here is &lt;a href="https://andrewlock.net/making-getoradd-on-concurrentdictionary-thread-safe-using-lazy/"&gt;a neat trick I've blogged about previously to make the &lt;code&gt;GetOrAdd&lt;/code&gt; function thread safe&lt;/a&gt;. The job of actually creating the handler occurs in &lt;code&gt;CreateHandlerEntry&lt;/code&gt; (which we'll see shortly) which creates an &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt; is &lt;em&gt;mostly&lt;/em&gt; an immutable object containing an &lt;code&gt;HttpMessageHandler&lt;/code&gt; and a DI &lt;code&gt;IServiceScope&lt;/code&gt;. In reality it also contains an internal timer that is used with the &lt;code&gt;StartExpiryTimer()&lt;/code&gt; method to call the provided &lt;code&gt;callback&lt;/code&gt; when the timer of length &lt;code&gt;Lifetime&lt;/code&gt; expires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;LifetimeTrackingHttpMessageHandler&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;Lifetime&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IServiceScope&lt;/span&gt; &lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartExpiryTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimerCallback&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Starts the internal timer&lt;/span&gt;
        &lt;span class="c1"&gt;// Executes the callback after Lifetime has expired.&lt;/span&gt;
        &lt;span class="c1"&gt;// If the timer has already started, is noop&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So the &lt;code&gt;CreateHandler&lt;/code&gt; method either creates a new &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt;, or retrieves the entry from the dictionary, and starts the timer. In the next section we'll look at how the &lt;code&gt;CreateHandlerEntry()&lt;/code&gt; method creates the &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt; instances:&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating and tracking HttpMessageHandlers in CreateHandlerEntry
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CreateHandlerEntry&lt;/code&gt; method is where the &lt;code&gt;HttpClient&lt;/code&gt; handler pipelines are created. It's a somewhat complex method, so I'll show it first, and then talk through it afterwards. The version shown below is somewhat simplified &lt;a href="https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs#L151"&gt;compared to the real version&lt;/a&gt;, but it maintains all the salient points.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The root service provider, injected into the constructor using DI&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// A collection of IHttpMessageHandler "configurers" that are added to every handler pipeline&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpMessageHandlerBuilderFilter&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;_filters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt; &lt;span class="nf"&gt;CreateHandlerEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IServiceScope&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
    &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;HttpClientFactoryOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_optionsMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;HttpMessageHandlerBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpMessageHandlerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// This is similar to the initialization pattern in:&lt;/span&gt;
    &lt;span class="c1"&gt;// https://github.com/aspnet/Hosting/blob/e892ed8bbdcd25a0dafc1850033398dc57f65fe1/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs#L188&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpMessageHandlerBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;--)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;configure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Wrap the handler so we can ensure the inner handler outlives the outer handler.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LifetimeTrackingHttpMessageHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerLifetime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpMessageHandlerBuilder&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpMessageHandlerBuilderActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpMessageHandlerBuilderActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There's quite a lot to unpack here. The method starts by creating a new &lt;code&gt;IServiceScope&lt;/code&gt; using the root DI container. This creates a DI scope, so that scoped services can be sourced from the associated &lt;code&gt;IServiceProvider&lt;/code&gt;. We also retrieve the &lt;code&gt;HttpClientFactoryOptions&lt;/code&gt; for the requested &lt;code&gt;HttpClient&lt;/code&gt; name, which contains the specific handler configuration for this instance.&lt;/p&gt;

&lt;p&gt;The next item retrieved from the container is an &lt;code&gt;HttpMessageHandlerBuilder&lt;/code&gt;, which by default is &lt;a href="https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpMessageHandlerBuilder.cs"&gt;a &lt;code&gt;DefaultHttpMessageHandlerBuilder&lt;/code&gt;&lt;/a&gt;. This is used to build the handler pipeline, by creating a "primary" handler, which is the &lt;code&gt;HttpClientHandler&lt;/code&gt; that is responsible for making the socket connection and sending the request. You can add additional &lt;code&gt;DelegatingHandler&lt;/code&gt;s that wrap the primary server, creating a pipeline for requests and responses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TPJWLbiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TPJWLbiS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_01.png" alt="Image of the delegating handlers and primary handler"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DelegatingHandler&lt;/code&gt;s are added to the builder using a slightly complicated arrangement, that is very reminiscent of how the middleware pipeline in an ASP.NET Core app is built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Configure()&lt;/code&gt; local method builds a pipeline of &lt;code&gt;DelegatingHandler&lt;/code&gt;s, based on the configuration you provided in &lt;code&gt;Startup.ConfigureServices()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IHttpMessageHandlerBuilderFilter&lt;/code&gt; are &lt;em&gt;filters&lt;/em&gt; that are injected into the &lt;code&gt;IHttpClientFactory&lt;/code&gt; constructor. They are used to add &lt;em&gt;additional&lt;/em&gt; handlers into the &lt;code&gt;DelegatingHandler&lt;/code&gt; pipeline. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;IHttpMessageHandlerBuilderFilter&lt;/code&gt; are directly analogous to the &lt;code&gt;IStartupFilter&lt;/code&gt;s used by the ASP.NET Core middleware pipeline, &lt;a href="https://andrewlock.net/exploring-istartupfilter-in-asp-net-core/"&gt;which I've talked about previously&lt;/a&gt;. A single &lt;code&gt;IHttpMessageHandlerBuilderFilter&lt;/code&gt; is registered by default, the &lt;code&gt;LoggingHttpMessageHandlerBuilderFilter&lt;/code&gt;. &lt;a href="https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/Logging/LoggingHttpMessageHandlerBuilderFilter.cs"&gt;This filter&lt;/a&gt; adds two additional handlers to the &lt;code&gt;DelegatingHandler&lt;/code&gt; pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LoggingScopeHttpMessageHandler&lt;/code&gt; at the start (outside) of the pipeline, which starts a new &lt;a href="https://docs.microsoft.com/aspnet/core/fundamentals/logging/#log-scopes"&gt;logging scope&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;LoggingHttpMessageHandler&lt;/code&gt; at the end (inside) of the pipeline, just before the request is sent to the primary &lt;code&gt;HttpClientHandler&lt;/code&gt;, which records logs about the request and response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, the whole pipeline is wrapped in &lt;a href="https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Microsoft.Extensions.Http/src/LifetimeTrackingHttpMessageHandler.cs"&gt;a &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt;, which is a "noop" &lt;code&gt;DelegatingHandler&lt;/code&gt;&lt;/a&gt;. We'll come back to the purpose of this handler later.&lt;/p&gt;

&lt;p&gt;The code (and probably my description) for &lt;code&gt;CreateHandlerEntry&lt;/code&gt; is definitely somewhat hard to follow, but the end result for an &lt;code&gt;HttpClient&lt;/code&gt; configured with two custom handlers (as demonstrated at the start of this post) is a handler pipeline that looks something like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tAVDUNqO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tAVDUNqO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/httpclientfactory_02.png" alt="The final build handler pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the handler pipeline is complete, it is saved in a new &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt; instance, along with the DI &lt;code&gt;IServiceScope&lt;/code&gt; used to create it, and given the lifetime defined in &lt;code&gt;HttpClientFactoryOptions&lt;/code&gt; (two minutes by default).&lt;/p&gt;

&lt;p&gt;This entry is returned to the caller (the &lt;code&gt;CreateHandler()&lt;/code&gt; method), added to the &lt;code&gt;ConcurrentDictionary&amp;lt;&amp;gt;&lt;/code&gt; of handlers, added to a new &lt;code&gt;HttpClient&lt;/code&gt; instance (in the &lt;code&gt;CreateClient()&lt;/code&gt; method), and returned to the original caller.&lt;/p&gt;

&lt;p&gt;For the next two minutes, every time you call &lt;code&gt;CreateClient()&lt;/code&gt;, you will get a &lt;em&gt;new&lt;/em&gt; instance of &lt;code&gt;HttpClient&lt;/code&gt;, but which has the &lt;em&gt;same&lt;/em&gt; handler pipeline as was originally created.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each &lt;em&gt;named&lt;/em&gt; or &lt;em&gt;typed&lt;/em&gt; client gets its own message handler pipeline. i.e. two instances of the &lt;code&gt;"github"&lt;/code&gt; named client will have the same handler chain, but the &lt;code&gt;"api"&lt;/code&gt; named client would have a different handler chain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next challenge is cleaning up and disposing the handler chain once the two minute timer has expired. This requires some careful handling, as you'll see in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up expired handlers.
&lt;/h2&gt;

&lt;p&gt;After two minutes, the timer stored in the &lt;code&gt;ActiveHandlerTrackingEntry&lt;/code&gt; entry will expire, and fire the &lt;code&gt;callback&lt;/code&gt; method passed into &lt;code&gt;StartExpiryTimer()&lt;/code&gt;: &lt;code&gt;ExpiryTimer_Tick()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ExpiryTimer_Tick&lt;/code&gt; is responsible for removing the active handler entry from the &lt;code&gt;ConcurrentDictionary&amp;lt;&amp;gt;&lt;/code&gt; pool, and adding it to a queue of expired handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Created in the constructor&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExpiredHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The Timer instance in ActiveHandlerTrackingEntry calls this when it expires&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ExpiryTimer_Tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="n"&gt;_activeHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryRemove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExpiredHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;StartCleanupTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the &lt;code&gt;active&lt;/code&gt; handler is removed from the &lt;code&gt;_activeHandlers&lt;/code&gt; collection, it will no longer be handed out with new &lt;code&gt;HttpClient&lt;/code&gt;s when your call &lt;code&gt;CreateClient()&lt;/code&gt;. But there are potentially &lt;code&gt;HttpClient&lt;/code&gt;s out there which are still using the &lt;code&gt;active&lt;/code&gt; handler. The &lt;code&gt;IHttpClientFactory&lt;/code&gt; has to wait for all the &lt;code&gt;HttpClient&lt;/code&gt; instances that reference this handler to be cleaned up before it can dispose the handler pipeline.&lt;/p&gt;

&lt;p&gt;The problem is, how can &lt;code&gt;IHttpClientFactory&lt;/code&gt; track that the handler is no longer referenced? The key is the use of the "noop" &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt;, and the &lt;code&gt;ExpiredHandlerTrackingEntry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ExpiredHandlerTrackingEntry&lt;/code&gt;, shown below, &lt;a href="https://docs.microsoft.com/dotnet/api/system.weakreference"&gt;creates a &lt;code&gt;WeakReference&lt;/code&gt;&lt;/a&gt; to the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt;. As I showed in the previous section, the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt; is the "outermost" handler in the chain, so it is the handler that is &lt;em&gt;directly&lt;/em&gt; referenced by the &lt;code&gt;HttpClient&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExpiredHandlerTrackingEntry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;WeakReference&lt;/span&gt; &lt;span class="n"&gt;_livenessTracker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// IMPORTANT: don't cache a reference to `other` or `other.Handler` here.&lt;/span&gt;
    &lt;span class="c1"&gt;// We need to allow it to be GC'ed.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ExpiredHandlerTrackingEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActiveHandlerTrackingEntry&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_livenessTracker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WeakReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;InnerHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;CanDispose&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;_livenessTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAlive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;HttpMessageHandler&lt;/span&gt; &lt;span class="n"&gt;InnerHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IServiceScope&lt;/span&gt; &lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;WeakReference&lt;/code&gt; to the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt; means that the &lt;em&gt;only direct references&lt;/em&gt; to the outermost handler in the chain are in &lt;code&gt;HttpClient&lt;/code&gt;s. Once all these &lt;code&gt;HttpClient&lt;/code&gt;s have been collected by the garbage collector, the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt; will have no references, and so will &lt;em&gt;also&lt;/em&gt; be disposed. The &lt;code&gt;ExpiredHandlerTrackingEntry&lt;/code&gt; can detect that via the &lt;code&gt;WeakReference.IsAlive&lt;/code&gt; property.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the &lt;code&gt;ExpiredHandlerTrackingEntry&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; maintain a reference to the rest of the handler pipeline, so that it can properly dispose the inner handler chain, as well as the DI &lt;code&gt;IServiceScope&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After an entry is added to the &lt;code&gt;_expiredHandlers&lt;/code&gt; queue, a timer is started by &lt;code&gt;StartCleanupTimer()&lt;/code&gt; which fires after 10 seconds. This calls the &lt;code&gt;CleanupTimer_Tick()&lt;/code&gt; method, which checks to see whether all the references to the handler have expired. If so, the handler chain and &lt;code&gt;IServiceScope&lt;/code&gt; are disposed. If not, they are added back onto the queue, and the clean-up timer is started again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CleanupTimer_Tick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Stop any pending timers, we'll restart the timer if there's anything left to process after cleanup.&lt;/span&gt;
    &lt;span class="nf"&gt;StopCleanupTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Loop through all the timers&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;initialCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;initialCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryDequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ExpiredHandlerTrackingEntry&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CanDispose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// All references to the handler chain have been removed&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// log the exception&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If the entry is still live, put it back in the queue so we can process it&lt;/span&gt;
            &lt;span class="c1"&gt;// during the next cleanup cycle.&lt;/span&gt;
            &lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// We didn't totally empty the cleanup queue, try again later.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_expiredHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;StartCleanupTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The method presented above is pretty simple - it loops through each of the &lt;code&gt;ExpiredHandlerTrackingEntry&lt;/code&gt; entries in the queue, and checks if all references to the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt; handlers have been removed. If they have, the handler chain (everything that was &lt;em&gt;inside&lt;/em&gt; the lifetime tracking handler) is disposed, as is the DI &lt;code&gt;IServiceScope&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If there are still live references to any of the &lt;code&gt;LifetimeTrackingHttpMessageHandler&lt;/code&gt; handlers, the entries are put back in the queue, and the cleanup timer is started again. Every 10 seconds another cleanup sweep is run.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I simplified the &lt;code&gt;CleanupTimer_Tick()&lt;/code&gt; method shown above &lt;a href="https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs#L260"&gt;compared to the original&lt;/a&gt;. The original adds additional logging, and uses locking to ensure only a single thread runs the cleanup at a time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That brings us to the end of this deep dive in the internals of &lt;code&gt;IHttpClientFactory&lt;/code&gt;! &lt;code&gt;IHttpClientFactory&lt;/code&gt; shows the &lt;em&gt;correct&lt;/em&gt; way to manage &lt;code&gt;HttpClient&lt;/code&gt; and &lt;code&gt;HttpMessageHandler&lt;/code&gt;s in your application. Having read through the code, it's understandable that noone got this completely right for so long - there's a lot of tricky gotchas there! If you've made it this far, I suggest going and &lt;a href="https://github.com/dotnet/runtime/blob/065bf96e5c298352c790b9a82564d57a745725f5/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs"&gt;looking through the original code&lt;/a&gt;, I highlighted (what I consider) the most important points in this post, but you can learn a lot from reading other people's code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I looked at the source code behind the default &lt;code&gt;IHttpClientFactory&lt;/code&gt; implementation in .NET Core 3.1, &lt;code&gt;DefaultHttpClientFactory&lt;/code&gt;. I showed how the factory stores an active &lt;code&gt;HttpMessageHandler&lt;/code&gt; pipeline for each configured named or typed client, with each new &lt;code&gt;HttpClient&lt;/code&gt; getting a reference to the same pipeline. I showed that the handler pipeline is built in a similar way to the ASP.NET Core middleware pipeline, using handlers. Finally, I showed how the factory tracks whether any &lt;code&gt;HttpClient&lt;/code&gt;s reference a pipeline instance by using a &lt;code&gt;WeakReference&lt;/code&gt; to the handler.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>httpclient</category>
      <category>dependencyinjection</category>
      <category>sourcecodedive</category>
    </item>
    <item>
      <title>Detecting duplicate routes in ASP.NET Core: Visualizing ASP.NET Core 3.0 endpoints using GraphvizOnline - Part 5</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 28 Jul 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/detecting-duplicate-routes-in-asp-net-core-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-5-2571</link>
      <guid>https://dev.to/andrewlocknet/detecting-duplicate-routes-in-asp-net-core-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-5-2571</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hMyi5qSv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hMyi5qSv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector2.png" alt="Detecting duplicate routes in ASP.NET Core"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://andrewlock.net/series/visualizing-asp-net-core-3-endpoints-using-graphvizonline/"&gt;this series&lt;/a&gt;, I have shown how to view all the routes in your application using an endpoint graph. In this post I show how to do something more useful—detecting duplicate routes that would throw an exception at runtime. I show how to use the same &lt;code&gt;DfaGraphWriter&lt;/code&gt; primitives as in previous posts to create a unit test that fails if you have any duplicate routes in your application that would cause a failure at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicate routes and runtime exceptions
&lt;/h2&gt;

&lt;p&gt;ASP.NET Core 3.x uses endpoint routing for all your MVC/API controllers and Razor Pages, as well as dedicated endpoints like a health check endpoint. As you've already seen in this series, those endpoints are built into a directed graph, which is used to route incoming requests to a handler:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" alt="A ValuesController endpoint routing application with different styling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, it's perfectly possible to structure your controllers/Razor Pages in such as way as to have multiple endpoints be a match for a given route. For example, imagine you have a "values" controller in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValuesController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"value1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"value2"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Meanwhile, some joker has also created the following controller too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DuplicateController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/values"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"oops"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As routing is only handled at runtime, everything will appear to be fine when you build and deploy your application. Health checks will pass, and all other routes will behave normally. But when someone hits the URL &lt;code&gt;/api/values&lt;/code&gt;, you'll get this beauty:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hMyi5qSv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hMyi5qSv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector2.png" alt="AmbiguousMatchException: The request matched multiple endpoints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The good thing here at least is that the error message tells you where the problem is. But wouldn't it be nice to know that &lt;em&gt;before&lt;/em&gt; you've deployed to production?&lt;/p&gt;

&lt;p&gt;Trying to detect this error ahead of time was what started me down the graph-drawing rabbit-hole of this series. My goal was to create a simple unit test that can run as part of the build, to identify errors like these.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a duplicate endpoint detector
&lt;/h2&gt;

&lt;p&gt;If you've read the rest of &lt;a href="https://andrewlock.net/series/visualizing-asp-net-core-3-endpoints-using-graphvizonline/"&gt;this series&lt;/a&gt;, then you may have already figured out how this is going to work. The &lt;code&gt;DfaGraphWriter&lt;/code&gt; that is built-in to the framework, which can be used to visualize the endpoint graph, knows how to visit each node (route) in the graph. We'll adapt this somewhat: instead of outputting a graph, we'll return a list of routes which match multiple endpoints.&lt;/p&gt;

&lt;p&gt;The detector class we'll create, &lt;code&gt;DuplicateEndpointDetector&lt;/code&gt; uses &lt;a href="https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and-reflection/"&gt;the same ImpromptuInterface technique as previous posts&lt;/a&gt;, reusing the &lt;code&gt;IDfaNode&lt;/code&gt; and &lt;code&gt;IDfaMatcherBuilder&lt;/code&gt; interfaces, shown below for completeness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IDfaNode&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Matches&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IDictionary&lt;/span&gt; &lt;span class="n"&gt;Literals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;CatchAll&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IDictionary&lt;/span&gt; &lt;span class="n"&gt;PolicyEdges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IDfaMatcherBuilder&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RouteEndpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;BuildDfaTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;includeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These interfaces give us a slightly nicer way to work with &lt;a href="https://github.com/dotnet/aspnetcore/blob/083f81d7604352822820f6313c3d4eb219c044a8/src/Http/Routing/src/Internal/DfaGraphWriter.cs"&gt;the internal classes used by the default &lt;code&gt;DfaGraphWriter&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The bulk of the &lt;code&gt;DuplicateEndpointDetector&lt;/code&gt; is identical to the &lt;code&gt;CustomDfaGraphWriter&lt;/code&gt; &lt;a href="https://andrewlock.net/creating-a-custom-endpoint-visualization-graph/"&gt;from my previous post&lt;/a&gt;. The only difference is that we a &lt;code&gt;GetDuplicateEndpoints()&lt;/code&gt; method instead of a &lt;code&gt;Write()&lt;/code&gt; method. We also don't use a &lt;code&gt;TextWriter&lt;/code&gt; (as we're not trying to build a graph for display) and we return a dictionary of duplicated endpoints (keyed on the route).&lt;/p&gt;

&lt;p&gt;I've shown almost the complete code below, with the exception of the &lt;code&gt;LogDuplicates&lt;/code&gt; method which we'll get to shortly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DuplicateEndpointDetector&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DuplicateEndpointDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetDuplicateEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EndpointDataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Use ImpromptuInterface to create the required dependencies as shown in previous post&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;matcherBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEndpointSelectorPolicy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.Routing.Matching.DfaMatcherBuilder"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the list of endpoints used to build the graph&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matcherBuilder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;IDfaMatcherBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaMatcherBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// This is the same logic as the original graph writer&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;RouteEndpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISuppressMatchingMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()?.&lt;/span&gt;&lt;span class="n"&gt;SuppressMatching&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the raw tree from the registered routes&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawTree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildDfaTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;includeLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawTree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Store a list of nodes that have already been visited &lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Store a dictionary of duplicates&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the graph by visiting each node, and calling LogDuplicates on each&lt;/span&gt;
        &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LogDuplicates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// done&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogDuplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* Details shown later in this post*/&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Identical to the version shown in the previous post&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Break cycles&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Break cycles&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;catchAll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catchAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This class follows the same approach as the graph writer. It uses &lt;em&gt;ImpromptuInterface&lt;/em&gt; to create an &lt;code&gt;IDfaMatcherBuilder&lt;/code&gt; proxy, which it uses to build a graph of all the endpoints in the system. It then visits each of the &lt;code&gt;IDfaNode&lt;/code&gt;s in the graph and calls the &lt;code&gt;LogDuplicates()&lt;/code&gt; method, passing in each node in turn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LogDuplicates()&lt;/code&gt; is a lot simpler than the &lt;code&gt;WriteNode()&lt;/code&gt; implementation &lt;a href="https://andrewlock.net/creating-a-custom-endpoint-visualization-graph/"&gt;from the previous post&lt;/a&gt;. All we're concerned about here is whether a node has multiple entries in its &lt;code&gt;Matches&lt;/code&gt; property. If it does, we have an ambiguous route, so we add it to the dictionary. I'm using the &lt;code&gt;DisplayName&lt;/code&gt; to identify the ambiguous endpoints, just as the exception message shown earlier does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LogDuplicates is a local method. visited and duplicates are defined in the calling function&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogDuplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add the node to the visited node dictionary if it isn't already&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate a zero-based integer label for the node&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// We can safely index into visited because this is a post-order traversal,&lt;/span&gt;
    &lt;span class="c1"&gt;// all of the children of this node are already in the dictionary.&lt;/span&gt;

    &lt;span class="c1"&gt;// Does this node have multiple matches?&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;matchCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matchCount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Add the node to the dictionary!&lt;/span&gt;
        &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After executing &lt;code&gt;GetDuplicateEndpoints()&lt;/code&gt; you'll have a dictionary containing all the duplicate routes in your application. The final thing to do is create a unit test, so that we can fail the build if we detect a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a unit test to detect duplicate endpoints
&lt;/h2&gt;

&lt;p&gt;I like to put this check in a unit test, because performance is much less of an issue. You &lt;em&gt;could&lt;/em&gt; check for duplicate routes on app startup or using a health check But given that we use reflection heavily this will add to startup time, and also given that the routes are fixed once you deploy your application, a unit test seems like the best place for it.&lt;/p&gt;

&lt;p&gt;Just as &lt;a href="https://andrewlock.net/adding-an-endpoint-graph-to-your-aspnetcore-application/"&gt;in a previous post where I &lt;em&gt;draw&lt;/em&gt; the graph in a unit test&lt;/a&gt;, in this post I'll use an "integration test" to check for duplicate endpoints in my main app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new xUnit project using VS or by running &lt;code&gt;dotnet new xunit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install &lt;em&gt;Microsoft.AspNetCore.Mvc.Testing&lt;/em&gt; by running &lt;code&gt;dotnet add package Microsoft.AspNetCore.Mvc.Testing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;&amp;lt;Project&amp;gt;&lt;/code&gt; element of your test project to &lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk.Web"&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reference your ASP.NET Core project from the test project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can now create a simple integration test using &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1#basic-tests-with-the-default-webapplicationfactory"&gt;the &lt;code&gt;WebApplicationFactory&amp;lt;&amp;gt;&lt;/code&gt; as a class fixture&lt;/a&gt;. I also inject &lt;code&gt;ITestOutputHelper&lt;/code&gt; so that we can list out the duplicate endpoints for the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DuplicateDetectorTest&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClassFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApiRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Inject the factory and the output helper&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApiRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITestOutputHelper&lt;/span&gt; &lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DuplicateDetectorTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITestOutputHelper&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ShouldNotHaveDuplicateEndpoints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create an instance of the detector using the IServiceProvider from the app&lt;/span&gt;
        &lt;span class="c1"&gt;// and get an instance of the endpoint data&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;detector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DuplicateEndpointDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpointData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EndpointDataSource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Find all the duplicates&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDuplicateEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpointData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Print the duplicates to the output&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keyValuePair&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allMatches&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyValuePair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Duplicate route: '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyValuePair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'. Matches: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;allMatches&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// If we have some duplicates, then fail the CI build!&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you run the test, the assertion will fail, and if you view the output, you'll see a list of the routes with issues, as well as the endpoints that caused the problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f6seUP7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f6seUP7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/duplicate_detector_1.png" alt="Using a unit test to detect duplicate endpoints"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're running your unit tests as part of your CI build, then you'll catch the issue &lt;em&gt;before&lt;/em&gt; it gets to production. Pretty handy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I show how you can build a duplicate endpoint detector using the endpoint graph building facilities included in ASP.NET Core. The end result requires quite a few tricks, as I've shown in the previous posts of this series, such as using &lt;em&gt;ImpromptuInterface&lt;/em&gt; to create proxies for interacting with &lt;code&gt;internal&lt;/code&gt; types.&lt;/p&gt;

&lt;p&gt;The end result is that you can detect when multiple endpoints (e.g. API controllers) are using the same route in your application. Given that those throw routes would throw an &lt;code&gt;AmbiguousMatchException&lt;/code&gt; at runtime, being able to detect issues using a unit test is pretty handy!&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>routing</category>
    </item>
    <item>
      <title>Creating a custom endpoint visualization graph: Visualizing ASP.NET Core 3.0 endpoints using GraphvizOnline - Part 4</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 21 Jul 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/creating-a-custom-endpoint-visualization-graph-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-4-5ekn</link>
      <guid>https://dev.to/andrewlocknet/creating-a-custom-endpoint-visualization-graph-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-4-5ekn</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IDtAmUmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/customgraph_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IDtAmUmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/customgraph_banner.png" alt="Creating a custom endpoint visualization graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/"&gt;this series&lt;/a&gt;, I've been laying the groundwork for building a custom endpoint visualization graph, as I showed &lt;a href="https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/"&gt;in my first post&lt;/a&gt;. This graph shows the different parts of the endpoint routes: literal values, parameters, verb constraints, and endpoints that generate a result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" alt="A ValuesController endpoint routing application with different styling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post I show how you can create an endpoint graph like this for your own application, by creating a custom &lt;code&gt;DfaGraphWriter&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post uses techniques and classes from the previous posts in the series, so I strongly suggest reading those before continuing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Adding configuration for the endpoint graph
&lt;/h2&gt;

&lt;p&gt;The first thing we'll look at is how to configure what the final endpoint graph will look like. We'll add configuration for two types of node and four types of edge. The edges are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Literal edges: Literal matches for route sections, such as &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;values&lt;/code&gt; in the route &lt;code&gt;api/values/{id}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Parameters edges: Parameterised sections for routes, such as &lt;code&gt;{id}&lt;/code&gt; in the route &lt;code&gt;api/values/{id}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Catch all edges: Edges that correspond to the catch-all route parameter, such as &lt;code&gt;{**slug}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Policy edges: Edges that correspond to a constraint other than the URL. For example, the HTTP verb-based edges in the graph, such as &lt;code&gt;HTTP: GET&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and the nodes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matching node: A node that is associated with an endpoint match, so will generate a response.&lt;/li&gt;
&lt;li&gt;Default node: A node that is &lt;em&gt;not&lt;/em&gt; associated with an endpoint match.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these nodes and edges can have &lt;a href="https://www.graphviz.org/doc/info/attrs.html"&gt;any number of Graphviz attributes&lt;/a&gt; to control their display. The &lt;code&gt;GraphDisplayOptions&lt;/code&gt; below show the default values I used to generate the graph at the start of this post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GraphDisplayOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for literal edges&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LiteralEdge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for parameter edges&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ParametersEdge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"arrowhead=diamond color=\"blue\""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for catchall parameter edges&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CatchAllEdge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"arrowhead=odot color=\"green\""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for policy edges&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PolicyEdge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"color=\"red\" style=dashed arrowhead=open"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for node which contains a match&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MatchingNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shape=box style=filled color=\"brown\" fontcolor=\"white\""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Additional display options for node without matches&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DefaultNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can now create our custom graph writer using this object to control the display, and using the &lt;em&gt;ImpromptuInterface&lt;/em&gt; "proxy" technique shown in &lt;a href="https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and-reflection/"&gt;the previous post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a custom DfaGraphWriter
&lt;/h2&gt;

&lt;p&gt;Our custom graph writer (cunningly called &lt;code&gt;CustomDfaGraphWriter&lt;/code&gt;) is heavily based on &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Internal/DfaGraphWriter.cs"&gt;the &lt;code&gt;DfaGraphWriter&lt;/code&gt; included in ASP.NET Core&lt;/a&gt;. The bulk of this class is the same as the original, with the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inject the &lt;code&gt;GraphDisplayOptions&lt;/code&gt; into the class to customise the display.&lt;/li&gt;
&lt;li&gt;Use the &lt;em&gt;ImpromptuInterface&lt;/em&gt; library to work with the internal &lt;code&gt;DfaMatcherBuilder&lt;/code&gt; and &lt;code&gt;DfaNode&lt;/code&gt; classes, &lt;a href="https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and-reflection/"&gt;as shown in the previous post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Customise the &lt;code&gt;WriteNode&lt;/code&gt; function to use our custom styles.&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;Visit&lt;/code&gt; function to work with the &lt;code&gt;IDfaNode&lt;/code&gt; interface, instead of using the &lt;code&gt;Visit()&lt;/code&gt; method on the internal &lt;code&gt;DfaNode&lt;/code&gt; class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole &lt;code&gt;CustomDfaGraphWriter&lt;/code&gt; is shown below, focusing on the main &lt;code&gt;Write()&lt;/code&gt; function. I've kept the implementation almost identical to &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Internal/DfaGraphWriter.cs"&gt;the original&lt;/a&gt;, only updating the parts we have to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomDfaGraphWriter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Inject the GraphDisplayOptions &lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;GraphDisplayOptions&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomDfaGraphWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GraphDisplayOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EndpointDataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Use ImpromptuInterface to create the required dependencies as shown in previous post&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;matcherBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEndpointSelectorPolicy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.Routing.Matching.DfaMatcherBuilder"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the list of endpoints used to build the graph&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matcherBuilder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;IDfaMatcherBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaMatcherBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// This is the same logic as the original graph writer&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;RouteEndpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISuppressMatchingMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()?.&lt;/span&gt;&lt;span class="n"&gt;SuppressMatching&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the raw tree from the registered routes&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawTree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildDfaTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;includeLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawTree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Store a list of nodes that have already been visited &lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the graph by visiting each node, and calling WriteNode on each&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"digraph DFA {"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WriteNode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* Write the node to the TextWriter */&lt;/span&gt;
            &lt;span class="cm"&gt;/* Details shown later in this post*/&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* Recursively visit each node in the tree. */&lt;/span&gt;
        &lt;span class="cm"&gt;/* Details shown later in this post*/&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I've elided the &lt;code&gt;Visit&lt;/code&gt; and &lt;code&gt;WriteNode&lt;/code&gt; functions here for brevity, but we'll look into them soon. We'll start with the &lt;code&gt;Visit&lt;/code&gt; function, as that flies closest to the original.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the Visit function to work with IDfaNode
&lt;/h2&gt;

&lt;p&gt;As I discussed in &lt;a href="https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and-reflection/"&gt;my previous post&lt;/a&gt;, one of the biggest problems creating a custom &lt;code&gt;DfaGraphWriter&lt;/code&gt; is its use of &lt;code&gt;internal&lt;/code&gt; classes. To work around that I used &lt;em&gt;ImpromptuInterface&lt;/em&gt; to create proxy objects that wrap the original:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0abetMl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/impromptuinterface.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0abetMl4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/impromptuinterface.png" alt="Using ImpromptuInterface to add a wrapper proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The original &lt;code&gt;Visit()&lt;/code&gt; method is &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Matching/DfaNode.cs#L80"&gt;a method on the &lt;code&gt;DfaNode&lt;/code&gt;&lt;/a&gt; class. It recursively visits every node in the endpoint tree, calling a provided &lt;code&gt;Action&amp;lt;&amp;gt;&lt;/code&gt; function for each node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As &lt;code&gt;DfaNode&lt;/code&gt; is &lt;code&gt;internal&lt;/code&gt;, I implemented the &lt;code&gt;Visit&lt;/code&gt; function as a static method on &lt;code&gt;CustomDfaGraphWriter&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our custom implementation is broadly the same as &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Matching/DfaNode.cs#L80"&gt;the original&lt;/a&gt;, but we have to do some somewhat arduous conversions between the "raw" &lt;code&gt;DfaNode&lt;/code&gt;s and our &lt;code&gt;IDfaNode&lt;/code&gt; proxies. The updated method is shown below. The method takes two parameters—the node being checked, and an &lt;code&gt;Action&amp;lt;&amp;gt;&lt;/code&gt; to run on each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Does the node of interest have any nodes connected by literal edges?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// node.Literals is actually a Dictionary&amp;lt;string, DfaNode&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Create a proxy for the child DfaNode node and visit it&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Does the node have a node connected by a parameter edge?&lt;/span&gt;
    &lt;span class="c1"&gt;// The reference check breaks any cycles in the graph&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a proxy for the DfaNode node and visit it&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Does the node have a node connected by a catch-all edge?&lt;/span&gt;
    &lt;span class="c1"&gt;// The refernece check breaks any cycles in the graph&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a proxy for the DfaNode node and visit it&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;catchAll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catchAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Does the node have a node connected by a policy edges?&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// node.PolicyEdges is actually a Dictionary&amp;lt;object, DfaNode&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;Visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Write the node using the provided Action&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Visit&lt;/code&gt; function uses a post-order traversal, so it traverses "deep" into a node's child nodes first before writing the node using the &lt;code&gt;visitor&lt;/code&gt; function. This is the same as &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Matching/DfaNode.cs#L80"&gt;the original &lt;code&gt;DfaNode.Visit()&lt;/code&gt; function&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're almost there now. We have a class that builds the endpoint node tree, traverses all the nodes in the tree, and runs a function for each. All that remains is to define the visitor function, &lt;code&gt;WriteNode()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining a custom WriteNode function
&lt;/h2&gt;

&lt;p&gt;We've &lt;em&gt;finally&lt;/em&gt; got to the meaty part, controlling how the endpoint graph is displayed. All of the customisation and effort so far has been to enable us to customise the &lt;code&gt;WriteNode&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WriteNode()&lt;/code&gt; is &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions"&gt;a local function&lt;/a&gt; that writes a node to the &lt;code&gt;TextWriter&lt;/code&gt; output, along with any connected edges, using the &lt;a href="https://en.wikipedia.org/wiki/DOT_(graph_description_language)"&gt;DOT graph description language&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our custom &lt;code&gt;WriteNode()&lt;/code&gt; function is, again, almost the same as &lt;a href="https://github.com/dotnet/aspnetcore/blob/0889a62250e56066055d45a6fce99413d3f48d56/src/Http/Routing/src/Internal/DfaGraphWriter.cs#L56-L94"&gt;the original&lt;/a&gt;. There are two main differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The original graph writer works with &lt;code&gt;DfaNode&lt;/code&gt;s, we have to convert to using the &lt;code&gt;IDfaNode&lt;/code&gt; proxy.&lt;/li&gt;
&lt;li&gt;The original graph writer uses the same styling for all nodes and edges. We customise the display of nodes and edges based on the configured &lt;code&gt;GraphDisplayOptions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;As &lt;code&gt;WriteNode&lt;/code&gt; is a local function, it can access variables from the enclosing function. This includes the &lt;code&gt;writer&lt;/code&gt; parameter, used to write the graph to output and the &lt;code&gt;visited&lt;/code&gt; dictionary of previously written nodes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following shows our (heavily commented) custom version of the &lt;code&gt;WriteNode()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// add the node to the visited node dictionary if it isn't already&lt;/span&gt;
    &lt;span class="c1"&gt;// generate a zero-based integer label for the node&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// We can safely index into visited because this is a post-order traversal,&lt;/span&gt;
    &lt;span class="c1"&gt;// all of the children of this node are already in the dictionary.&lt;/span&gt;

    &lt;span class="c1"&gt;// If this node is linked to any nodes by a literal edge&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DictionaryEntry&lt;/span&gt; &lt;span class="n"&gt;dictEntry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Foreach linked node, get the label for the edge and the linked node&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;edgeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;dictEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nodeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="c1"&gt;// Write an edge, including our custom styling for literal edges&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nodeLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [label=\"/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;edgeLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LiteralEdge&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If this node is linked to a nodes by a parameter edge&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nodeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;catchAll&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Write an edge labelled as /* using our custom styling for parameter edges&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nodeLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [label=\"/**\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAllEdge&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If this node is linked to a catch-all edge&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="n"&gt;catchAll&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nodeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;catchAll&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Write an edge labelled as /** using our custom styling for catch-all edges&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nodelLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [label=\"/**\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatchAllEdge&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If this node is linked to any Policy Edges&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DictionaryEntry&lt;/span&gt; &lt;span class="n"&gt;dictEntry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Foreach linked node, get the label for the edge and the linked node&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;edgeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;dictEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;IDfaNode&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dictEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActLike&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDfaNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nodeLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="c1"&gt;// Write an edge, including our custom styling for policy edges&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nodeLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [label=\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PolicyEdge&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Does this node have any associated matches, indicating it generates a response?&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;matchCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;extras&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matchCount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchingNode&lt;/span&gt; &lt;span class="c1"&gt;// If we have matches, use the styling for response-generating nodes...&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ...otherwise use the default style&lt;/span&gt;

    &lt;span class="c1"&gt;// Write the node to the graph output&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [label=\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Tracing the flow of these interactions can be a little confusing, because of the way we write the nodes from the "leaf" nodes back to the root of the tree. For example if we look at the output for the basic app shown at the start of this post, you can see the "leaf" endpoints are all written first: the &lt;code&gt;healthz&lt;/code&gt; health check endpoint and the terminal match generating endpoints with the longest route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;DFA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;diamond&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Even though the leaf nodes are written to the graph output first, the Graphviz visualizer will generally draw the graph with the leaf nodes at the &lt;em&gt;bottom&lt;/em&gt;, and the edges pointing down. You can visualize the graph online at &lt;a href="https://dreampuf.github.io/GraphvizOnline/"&gt;https://dreampuf.github.io/GraphvizOnline/&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" alt="A ValuesController endpoint routing application with different styling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to change how the graph is rendered you can customize the &lt;code&gt;GraphDisplayOptions&lt;/code&gt;. If you use the "test" approach I described in a previous post, you can pass these options in directly when generating the graph. If you're using the "middleware" approach, you can register the &lt;code&gt;GraphDisplayOptions&lt;/code&gt; using the &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt; system instead, and control the display using the configuration system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I showed how to create a custom &lt;code&gt;DfaGraphWriter&lt;/code&gt; to control how an application's endpoint graph is generated. To interoperate with the &lt;code&gt;internal&lt;/code&gt; classes we used &lt;em&gt;ImpromptuInterface&lt;/em&gt;, as described in &lt;a href="https://andrewlock.net/creating-a-custom-dfagraphwriter-using-impromptuinterface-and-reflection/"&gt;the previous post&lt;/a&gt;, to create proxies we can interact with. We then had to write a custom &lt;code&gt;Visit()&lt;/code&gt; function to work with the &lt;code&gt;IDfaNode&lt;/code&gt; proxies. Finally, we created a custom &lt;code&gt;WriteNode&lt;/code&gt; function that uses custom settings defined in a &lt;code&gt;GraphDisplayOptions&lt;/code&gt; object to display each type of node and edge differently.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>routing</category>
    </item>
    <item>
      <title>Adding an endpoint graph to your ASP.NET Core application: Visualizing ASP.NET Core 3.0 endpoints using GraphvizOnline - Part 2</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 07 Jul 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/adding-an-endpoint-graph-to-your-asp-net-core-application-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-2-1efp</link>
      <guid>https://dev.to/andrewlocknet/adding-an-endpoint-graph-to-your-asp-net-core-application-visualizing-asp-net-core-3-0-endpoints-using-graphvizonline-part-2-1efp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UlCElT7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UlCElT7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_banner.png" alt="Adding an endpoint graph to your ASP.NET Core application"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;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 &lt;a href="https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/#understanding-the-different-types-of-node-"&gt;my previous post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using DfaGraphWriter to visualize your endpoints.
&lt;/h2&gt;

&lt;p&gt;ASP.NET Core comes &lt;a href="https://github.com/dotnet/aspnetcore/blob/36856ca8f9932b88757bbf24904bf36868b4d727/src/Http/Routing/src/Internal/DfaGraphWriter.cs"&gt;with a handy class, &lt;code&gt;DfaGraphWriter&lt;/code&gt;&lt;/a&gt;, that can be used to visualize your routes in an ASP.NET Core 3.x application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DfaGraphWriter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EndpointDataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;For now we'll create a middleware that uses the &lt;code&gt;DfaGraphWriter&lt;/code&gt; to write the graph as an HTTP response. You can inject both the &lt;code&gt;DfaGraphWriter&lt;/code&gt; and the &lt;code&gt;EndpointDataSource&lt;/code&gt; it uses into the constructor using DI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GraphEndpointMiddleware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// inject required services using DI&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;DfaGraphWriter&lt;/span&gt; &lt;span class="n"&gt;_graphWriter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;EndpointDataSource&lt;/span&gt; &lt;span class="n"&gt;_endpointData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GraphEndpointMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;RequestDelegate&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;DfaGraphWriter&lt;/span&gt; &lt;span class="n"&gt;graphWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;EndpointDataSource&lt;/span&gt; &lt;span class="n"&gt;endpointData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_graphWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graphWriter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_endpointData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;endpointData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// set the response&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Write the response into memory&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringWriter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Write the graph&lt;/span&gt;
            &lt;span class="n"&gt;_graphWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_endpointData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Write the graph to the response&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;StringWriter&lt;/code&gt;, convert it to a &lt;code&gt;string&lt;/code&gt;, and &lt;em&gt;then&lt;/em&gt; write it to the graph.&lt;/p&gt;

&lt;p&gt;This is all necessary because the &lt;code&gt;DfaGraphWriter&lt;/code&gt; writes to the &lt;code&gt;TextWriter&lt;/code&gt; using &lt;em&gt;synchronous&lt;/em&gt; &lt;code&gt;Stream&lt;/code&gt; API calls, like &lt;code&gt;Write&lt;/code&gt;, instead of &lt;code&gt;WriteAsync&lt;/code&gt;. Ideally, we would be able to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a stream writer that wraps the body&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// write asynchronously to the stream&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_graphWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_endpointData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;DfaGraphWriter&lt;/code&gt; used asynchronous APIs, then you could write directly to &lt;code&gt;Response.Body&lt;/code&gt; as shown above and avoid the in-memory &lt;code&gt;string&lt;/code&gt;. Unfortunately, it's synchronous, and &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#avoid-synchronous-read-or-write-on-httprequesthttpresponse-body"&gt;you shouldn't write to &lt;code&gt;Response.Body&lt;/code&gt; using synchronous calls for performance reasons&lt;/a&gt;. If you &lt;em&gt;try&lt;/em&gt; to use pattern above then you &lt;em&gt;may&lt;/em&gt; get an &lt;code&gt;InvalidOperationException&lt;/code&gt; like the following, depending on the size of the graph being written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You might &lt;strong&gt;not&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add it as an endpoint using endpoint routing.&lt;/li&gt;
&lt;li&gt;Add it as a simple "branch" from your middleware pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The former approach is the generally recommended method for adding endpoints to ASP.NET Core 3.0 apps, so lets start there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the graph visualizer as an endpoint
&lt;/h3&gt;

&lt;p&gt;To simplify the endpoint registration code, I'll create a simple extension method for adding the &lt;code&gt;GraphEndpointMiddleware&lt;/code&gt; as an endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GraphEndpointMiddlewareExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEndpointConventionBuilder&lt;/span&gt; &lt;span class="nf"&gt;MapGraphVisualisation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEndpointRouteBuilder&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphEndpointMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithDisplayName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Endpoint Graph"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can then add the graph endpoint to our ASP.NET Core application by calling &lt;code&gt;MapGraphVisualisation("/graph")&lt;/code&gt; in the &lt;code&gt;UseEndpoints()&lt;/code&gt; method in &lt;code&gt;Startup.Configure()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Add the graph endpoint&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGraphVisualisation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/graph"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's all we need to do. The &lt;code&gt;DfaGraphWriter&lt;/code&gt; is already available in DI, so there's no additional configuration required there. Navigating to &lt;code&gt;http://localhost:5000/graph&lt;/code&gt; (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/):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;DFA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/graph/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/graph&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Which can be visualized using GraphVizOnline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zprlj98R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zprlj98R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_01.png" alt="A ValuesController endpoint routing application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exposing the graph as an endpoint in the endpoint routing system has both pros and cons:&lt;/p&gt;

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

&lt;p&gt;If that final point is a deal breaker for you, you &lt;em&gt;could&lt;/em&gt; use the old-school way of creating endpoints, using branching middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the graph visualizer as a middleware branch
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;To create a middleware branch, use the &lt;code&gt;Map()&lt;/code&gt; command. For example, you could add a branch using the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// add the graph endpoint as a branch of the pipeline&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/graph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphEndpointMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" alt="A ValuesController endpoint routing application without the graph endpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For me, it doesn't make a &lt;em&gt;lot&lt;/em&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating an endpoint graph from an integration test
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests"&gt;ASP.NET Core has a good story for running in-memory integration tests&lt;/a&gt;, exercising your full middleware pipeline and API controllers/Razor Pages, &lt;em&gt;without&lt;/em&gt; having to make network calls.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1#basic-tests-with-the-default-webapplicationfactory"&gt;the &lt;code&gt;WebApplicationFactory&amp;lt;&amp;gt;&lt;/code&gt; facility&lt;/a&gt;, in &lt;em&gt;Microsoft.AspNetCore.Mvc.Testing&lt;/em&gt; which exposes the underlying DI container. This allows you to run code in the DI context of the application, but from a unit test.&lt;/p&gt;

&lt;p&gt;To try it out&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new xUnit project (my testing framework of choice) using VS or by running &lt;code&gt;dotnet new xunit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;em&gt;Microsoft.AspNetCore.Mvc.Testing&lt;/em&gt; by running &lt;code&gt;dotnet add package Microsoft.AspNetCore.Mvc.Testing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;&amp;lt;Project&amp;gt;&lt;/code&gt; element of the test project to &lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk.Web"&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reference your ASP.NET Core project from the test project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;code&gt;WebApplicationFactory&amp;lt;&amp;gt;&lt;/code&gt; as a class fixture; if you need to customise the factory, see &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests#customize-webapplicationfactory"&gt;the docs&lt;/a&gt; or &lt;a href="https://andrewlock.net/converting-integration-tests-to-net-core-3/"&gt;my previous post&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;In addition to &lt;code&gt;WebApplicationFactory&amp;lt;&amp;gt;&lt;/code&gt;, I'm also injecting &lt;code&gt;ITestOutputHelper&lt;/code&gt;. You need to use this class to record test output with xUnit. &lt;a href="https://xunit.net/docs/capturing-output"&gt;Writing directly to &lt;code&gt;Console&lt;/code&gt; won't work.&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateGraphTest&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClassFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApiRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Inject the factory and the output helper&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ApiRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITestOutputHelper&lt;/span&gt; &lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;GenerateGraphTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITestOutputHelper&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GenerateGraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// fetch the required services from the root container of the app&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;graphWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DfaGraphWriter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpointData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EndpointDataSource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// build the graph as before&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringWriter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;graphWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpointData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// write the graph to the test output&lt;/span&gt;
            &lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7AQRTBY8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7AQRTBY8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graphs_2_02.png" alt="Viewing endpoint data from a test in Visual Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I find a simple test like this is often sufficient for my purposes. There's lots of advantages in my eyes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't expose this data as an endpoint&lt;/li&gt;
&lt;li&gt;Has no impact on your application&lt;/li&gt;
&lt;li&gt;Can be easily generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can't generate the graph in an IHostedService
&lt;/h2&gt;

&lt;p&gt;Generally speaking, you can access the &lt;code&gt;DfaGraphWriter&lt;/code&gt; and &lt;code&gt;EndpointDataSource&lt;/code&gt; services from anywhere in your app that uses dependency injection, or that has access to an &lt;code&gt;IServiceProvider&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Where you must be careful is if you're trying to generate the graph &lt;em&gt;early&lt;/em&gt; in your application's lifecycle. This applies particularly to &lt;code&gt;IHostedService&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;IHostedService&lt;/code&gt; in your application. In most cases, this shouldn't have a big impact, but it changes the &lt;em&gt;order&lt;/em&gt; that your application is built, compared to ASP.NET Core 2.x.&lt;/p&gt;

&lt;p&gt;In ASP.NET Core 2.x, the following things would happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The middleware pipeline is built.&lt;/li&gt;
&lt;li&gt;The server (Kestrel) starts listening for requests.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;IHostedService&lt;/code&gt; implementations are started.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, on ASP.NET Core 3.x, you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;IHostedService&lt;/code&gt; implementations are started.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;GenericWebHostService&lt;/code&gt; is started:

&lt;ul&gt;
&lt;li&gt;The middleware pipeline is built&lt;/li&gt;
&lt;li&gt;The server (Kestrel) starts listening for requests.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing to note is that the middleware pipeline isn't built until &lt;em&gt;after&lt;/em&gt; your &lt;code&gt;IHostedService&lt;/code&gt;s are executed. As &lt;code&gt;UseEndpoints()&lt;/code&gt; has not yet been called, &lt;code&gt;EndpointDataSource&lt;/code&gt; won't contain any data!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;EndpointDataSource&lt;/code&gt; will be empty if you try and generate your graph using &lt;code&gt;DfaGraphWriter&lt;/code&gt; from an &lt;code&gt;IHostedService&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same goes if you try and use another standard mechanisms for injecting early behaviour, like &lt;code&gt;IStartupFilter&lt;/code&gt;—these execute &lt;em&gt;before&lt;/em&gt; &lt;code&gt;Startup.Configure()&lt;/code&gt; is called, so the &lt;code&gt;EndpointDataSource&lt;/code&gt; will be empty.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Whichever approach you use, you're stuck only being able to generate the "default" endpoint graphs shown in this post. As I mentioned &lt;a href="https://andrewlock.net/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language/"&gt;in my previous post&lt;/a&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I showed how to use the &lt;code&gt;DfaGraphWriter&lt;/code&gt; and &lt;code&gt;EndpointDataSource&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

</description>
      <category>aspnetcore</category>
      <category>routing</category>
    </item>
    <item>
      <title>Visualizing ASP.NET Core endpoints using GraphvizOnline and the DOT language</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 30 Jun 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language-28n1</link>
      <guid>https://dev.to/andrewlocknet/visualizing-asp-net-core-endpoints-using-graphvizonline-and-the-dot-language-28n1</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t-9Oa2Ma--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t-9Oa2Ma--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_banner.png" alt="Visualizing ASP.NET Core endpoints using GraphvizOnline and the DOT language"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post I show how the endpoint routes in an ASP.NET Core 3.0 application can be visualized using the &lt;a href="http://dreampuf.github.io/GraphvizOnline"&gt;GraphvizOnline&lt;/a&gt; service. That lets you create diagrams like the following, which describe all the endpoints in your application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" alt="An example endpoint routing graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing graphs with GraphvizOnline and the DOT language
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dreampuf.github.io/GraphvizOnline"&gt;GraphvizOnline&lt;/a&gt; is &lt;a href="https://github.com/dreampuf/GraphvizOnline"&gt;a GitHub project&lt;/a&gt; that provides an online vizualizer for viewing graphs specified in the &lt;a href="https://en.wikipedia.org/wiki/DOT_(graph_description_language)"&gt;DOT graph description language&lt;/a&gt;. This is a simple language that lets you define various types of graph, which connects &lt;em&gt;nodes&lt;/em&gt; with &lt;em&gt;edges&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example, a basic &lt;em&gt;undirected graph&lt;/em&gt; could be defined as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="nx"&gt;MyGraph&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which describes the following graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--28Mu4j5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--28Mu4j5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_02.png" alt="A simple undirected graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each node has a name (&lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;d&lt;/code&gt;), and &lt;code&gt;--&lt;/code&gt; defines the edges between the nodes. The edges define connections between nodes, but they don't have a direction (hence the name, &lt;em&gt;undirected&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;You can also define a &lt;em&gt;directed&lt;/em&gt; graph, in which the edges &lt;em&gt;do&lt;/em&gt; have a direction. For a directed edge use &lt;code&gt;-&amp;gt;&lt;/code&gt; instead of &lt;code&gt;--&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;MyGraph&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which describes the following graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fXz0hB_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fXz0hB_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_03.png" alt="A simple directed graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can customise the way the nodes and edges are displayed in multiple ways. For example, you can label the nodes and edges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;MySimpleGraph&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// The label attribute can be used to change the label of a node...&lt;/span&gt;
   &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="c1"&gt;// ... or an edge&lt;/span&gt;
   &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Baz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lTOKMzH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lTOKMzH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_04.png" alt="A labelled graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's &lt;a href="http://www.graphviz.org/doc/info/attrs.html"&gt;way more&lt;/a&gt; you can do with the &lt;a href="https://en.wikipedia.org/wiki/DOT_(graph_description_language)"&gt;DOT graph description language&lt;/a&gt;, but that's all we need for now. So how does this apply to ASP.NET Core?&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing ASP.NET Core endpoints as a directed graph
&lt;/h2&gt;

&lt;p&gt;The endpoint routing system in ASP.NET Core effectively works by creating a directed graph of the endpoint URL segments. Incoming requests are then matched, a segment at a time, to the graph to determine the endpoint to execute.&lt;/p&gt;

&lt;p&gt;For example, the following simple directed graph represents the endpoints in the default ASP.NET Core 3.0 Razor Pages application template (&lt;code&gt;dotnet new webapp&lt;/code&gt;), which contains three Razor Pages: &lt;em&gt;Index.cshtml&lt;/em&gt;, &lt;em&gt;Error.cshtml&lt;/em&gt; and &lt;em&gt;Privacy.cshtml&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;DFA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Error/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Index/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Privacy/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Privacy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which describes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dI9Vs1U3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dI9Vs1U3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_05.png" alt="A basic Razor Pages application"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the DOT file above, the nodes are given sequential integer names, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt; etc and are labelled using the endpoint name. This is the format ASP.NET Core uses for representing the endpoint graph.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For Razor Pages, the routing is very simple, so the graph is pretty obvious. A more interesting graph is produced by an API application. For example, the &lt;code&gt;ValuesController&lt;/code&gt; implementation shown below is similar to the version included in the ASP.NET Core 2.0 templates. It uses multiple HTTP verbs, as well as a slightly more complex URL structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValuesController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GET api/values&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"value1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"value2"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// GET api/values/5&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// POST api/values&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// PUT api/values/5&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// DELETE api/values/5&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For good measure, I also added a basic health check endpoint to &lt;code&gt;UseEndpoints()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This app produces the following graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;DFA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Which is visualised as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XpV5NAqj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_06.png" alt="A ValuesController endpoint routing application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a lot more going on in this graph, as we now have variable route parameter values (&lt;code&gt;{id}&lt;/code&gt; in the route templates, shown as &lt;code&gt;{...}&lt;/code&gt; in the graph) and HTTP verb constraints (&lt;code&gt;GET&lt;/code&gt;/&lt;code&gt;PUT&lt;/code&gt;/&lt;code&gt;POST&lt;/code&gt; etc).&lt;/p&gt;

&lt;p&gt;When I first saw this graph, I struggled to understand it. Is every node an endpoint? Surely not, as &lt;code&gt;/api/&lt;/code&gt; shouldn't generate a response. And what about the &lt;code&gt;HTTP: *&lt;/code&gt; endpoints, do they generate a response?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To understand further, I went spelunking into &lt;a href="https://github.com/dotnet/aspnetcore/blob/master/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs"&gt;the ASP.NET Core code that can generate these graphs&lt;/a&gt;, but it's a bit involved, and unfortunately not overly amenable to experimentation, due to extensive use of &lt;code&gt;internal&lt;/code&gt; classes. I explore this code in a later post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To better grasp the endpoint graph, we need to understand that not all the nodes are the same. In the next section we'll dig into the different types of node in even this simple graph, and then look at a better graphical representation (in my opinion at least!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the different types of node.
&lt;/h2&gt;

&lt;p&gt;Each node in the graph is associated with a given "depth". This is the number of URL segments that should already be matched. For example, the &lt;code&gt;/api/Values/&lt;/code&gt; node has a depth of 2—it requires the empty segment &lt;code&gt;/&lt;/code&gt; and the &lt;code&gt;/api&lt;/code&gt; segment to already be matched.&lt;/p&gt;

&lt;p&gt;When a request reaches the &lt;code&gt;EndpointRoutingMiddleware&lt;/code&gt; (added by &lt;code&gt;UseRouting()&lt;/code&gt;), the incoming request URL is compared against this graph. A path is attempted to be found through the graph, starting with the root node at the top of the tree. URL segments are incrementally matched to edges in the graph, and a path is traversed through the graph until the whole request URL is matched.&lt;/p&gt;

&lt;p&gt;Each node (represented by &lt;a href="https://github.com/dotnet/aspnetcore/blob/cc3d47f5501cdfae3e5b5be509ef2c0fb8cca069/src/Http/Routing/src/Matching/DfaNode.cs"&gt;the &lt;code&gt;DfaNode&lt;/code&gt; in ASP.NET Core&lt;/a&gt; internally) has several properties . The properties we're interested in for now are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Matches&lt;/code&gt;: This is the &lt;code&gt;Endpoint&lt;/code&gt;(s) associated with the node. If this node is matched via routing, then this is the &lt;code&gt;Endpoint&lt;/code&gt; that will be selected for execution. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Literals&lt;/code&gt;: These are the edges that connect nodes. If a &lt;code&gt;DfaNode&lt;/code&gt; has any &lt;code&gt;Literals&lt;/code&gt;, it has literal segments that can be further traversed to reached other nodes. For example, the &lt;code&gt;/api/&lt;/code&gt; node contains a single &lt;code&gt;Literal&lt;/code&gt; with value &lt;code&gt;/Values&lt;/code&gt;, pointing to the &lt;code&gt;/api/Values&lt;/code&gt; node.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PolicyEdges&lt;/code&gt;: These are edges that match based on a constraint other than the URL. For example, the verb-based edges in the graph, such as &lt;code&gt;HTTP: GET&lt;/code&gt;, are policy edges, that point to a different &lt;code&gt;DfaNode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Parameters&lt;/code&gt;: If the node has an edge that supports route parameters (e.g. &lt;code&gt;{id}&lt;/code&gt;), &lt;code&gt;Parameters&lt;/code&gt; points to the node that handles matching the parameters. This is represented in the graph by the edge &lt;code&gt;/*&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;There is an additional property, &lt;code&gt;CatchAll&lt;/code&gt;, which is relevant in some graphs, but I'll ignore it for now, as it's not required for our API graph.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Based on these properties, we can add some additional richness to our graph, by making use of some of &lt;a href="http://www.graphviz.org/doc/info/attrs.html"&gt;the other features in the DOT language&lt;/a&gt;, such as shapes, colours, line type and arrowheads:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddOMKmlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/graph_07.png" alt="A ValuesController endpoint routing application with different styling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This graph makes the following additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nodes which don't have any associated &lt;code&gt;Endpoint&lt;/code&gt; are shown in the default style, black bubbles.&lt;/li&gt;
&lt;li&gt;Nodes with &lt;code&gt;Matches&lt;/code&gt; are shown as filled, brown, boxes. These are nodes with an &lt;code&gt;Endpoint&lt;/code&gt;, that can generate a response. For the API example above, this applies to the nodes where a verb has been selected, as well as the health check endpoint. &lt;/li&gt;
&lt;li&gt;Literal segment edges are shown as default black edges, with a filled arrowhead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Parameters&lt;/code&gt; edges (&lt;code&gt;/*&lt;/code&gt;) are shown in blue, using a diamond arrowhead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PolicyEdges&lt;/code&gt; are shown in red, with a dashed line, and an empty triangle arrowhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, I freely admit my design skills suck, but nevertheless I think you can agree that this graph shows way more information than the default! 🙂 This is the definition that generated the graph above, remember you can visualise and play with the display yourself using &lt;a href="https://dreampuf.github.io/GraphvizOnline/"&gt;an online editor&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;digraph&lt;/span&gt; &lt;span class="nx"&gt;DFA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/{...}/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/ HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;fontcolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;diamond&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP: *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;dashed&lt;/span&gt; &lt;span class="nx"&gt;arrowhead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/Values/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/healthz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note the &lt;code&gt;"HTTP: *"&lt;/code&gt; nodes are associated with an endpoint, even though you might not expect it, because they return a &lt;code&gt;405 Method Not Allowed&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the next post, I'll show how you can generate endpoint graphs for your own ASP.NET Core applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I provided an introduction to the DOT language for describing graphs, and showed how you can use &lt;a href="https://dreampuf.github.io/GraphvizOnline"&gt;online editors&lt;/a&gt; to create images from the graphs. I then showed how routing for endpoints in an ASP.NET Core 3.x application can be represented as a directed graph. I described the differences between various nodes and edges in the endpoint graph, and tweaked the graph's display to better represent these differences. In later posts I'll show how you can generate your own endpoint graphs for your application, how to customise the display, and how to do more than just view the graphs.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>routing</category>
    </item>
    <item>
      <title>Getting started with ASP.NET Core</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 23 Jun 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/getting-started-with-asp-net-core-1iff</link>
      <guid>https://dev.to/andrewlocknet/getting-started-with-asp-net-core-1iff</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wspspyxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/aspnetcoreinaction.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wspspyxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/aspnetcoreinaction.png" alt="Getting started with ASP.NET Core"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.manning.com/meap-program"&gt;Manning Early Access Program (MEAP)&lt;/a&gt; has started for the second edition of my book &lt;a href="https://www.manning.com/books/asp-net-core-in-action-second-edition?a_aid=aspnetcore-in-action&amp;amp;a_bid=44c089ee"&gt;ASP.NET Core in Action, Second Edition&lt;/a&gt;. This post gives you a sample of what you can find in the book. If you like what you see, please take a look - for now you can even get a 40% discount with the code &lt;strong&gt;bllock2&lt;/strong&gt;. On top of that, you'll also get a copy of the first edition, free!&lt;/p&gt;

&lt;p&gt;The Manning Early Access Program provides you full access to books as they are written, You get the chapters as they are produced, plus the finished eBook as soon as it’s ready, and the paper book long before it's in bookstores. You can also interact with the author (me!) &lt;a href="https://livebook.manning.com/#!/book/asp-net-core-in-action-second-edition/discussion"&gt;on the forums&lt;/a&gt; to provide feedback as the book is being written.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to choose ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;This article assumes that you have a general grasp of what ASP.NET Core is and how it was designed. You might be wondering: should you use it? Microsoft is recommending that all new .NET web development should use ASP.NET Core, but switching to or learning a new web stack is a big ask for any developer or company. In this article I cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What sort of applications you can build with ASP.NET Core&lt;/li&gt;
&lt;li&gt;Some of the highlights of ASP.NET Core&lt;/li&gt;
&lt;li&gt;Why you should consider using ASP.NET Core for new applications&lt;/li&gt;
&lt;li&gt;Things to consider before converting existing ASP.NET applications to ASP.NET Core&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What type of applications can you build?
&lt;/h3&gt;

&lt;p&gt;ASP.NET Core provides a generalized web framework that can be used for a variety of applications. It can most obviously be used for building rich, dynamic websites, whether they’re e-commerce sites, content-based sites, or large n-tier applications—much the same as the previous version of ASP.NET.&lt;/p&gt;

&lt;p&gt;When .NET Core was originally released, there were few third-party libraries available for building these types of complex applications. After several years of active development, that’s no longer the case. Many developers have updated their libraries to work with ASP.NET Core, and many other libraries have been created to target ASP.NET Core specifically. For example, the open source content management system (CMS), &lt;a href="https://www.orchardproject.net/"&gt;Orchard&lt;/a&gt; has been redeveloped as &lt;a href="https://www.orchardcore.net/"&gt;Orchard Core&lt;/a&gt; to run on ASP.NET Core. In contrast, &lt;a href="https://www.cloudscribe.com/"&gt;the cloudscribe CMS project&lt;/a&gt; (figure 1) was written specifically for ASP.NET Core from its inception.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lTuP6fBP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F04_Lock.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lTuP6fBP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F04_Lock.png" alt="Figure 1"&gt;&lt;/a&gt;&lt;br&gt;&lt;em&gt;Figure 1. The .NET Foundation website (&lt;a href="https://dotnetfoundation.org/"&gt;&lt;/a&gt;&lt;a href="https://dotnetfoundation.org/"&gt;https://dotnetfoundation.org/&lt;/a&gt;) is build using the cloudscribe CMS and ASP.NET Core&lt;/em&gt;
  &lt;/p&gt;

&lt;p&gt;Traditional, page-based server-side-rendered web applications are the bread and butter of ASP.NET development, both with the previous version of ASP.NET and with ASP.NET Core. Additionally, single-page applications (SPAs), which use a client-side framework that commonly talks to a REST server, are easy to create with ASP.NET Core. Whether you’re using Angular, Vue, React, or some other client-side framework, it’s easy to create an ASP.NET Core application to act as the server-side API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DEFINITION&lt;/strong&gt; REST stands for Representational State Transfer. RESTful applications typically use lightweight and stateless HTTP calls to read, post (create/update), and delete data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ASP.NET Core isn’t restricted to creating RESTful services. It’s easy to create a web service or remote procedure call (RPC)-style service for your application, depending on your requirements, as shown in figure 2. In the simplest case, your application might expose only a single endpoint, narrowing its scope to become a microservice. ASP.NET Core is perfectly designed for building simple services thanks to its cross-platform support and lightweight design.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JmUgRKB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F05_Lock.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JmUgRKB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F05_Lock.png" alt="Figure 2"&gt;&lt;/a&gt;&lt;br&gt;
  &lt;em&gt;Figure 2. ASP.NET Core can act as the server-side application for a variety of different clients: it can serve HTML pages for traditional web applications, it can act as a REST API for client-side SPA applications, or it can act as an ad-hoc RPC service for client applications.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You should consider multiple factors when choosing a platform, not all of which are technical. One such factor is the level of support you can expect to receive from its creators. For some organizations, this can be one of the main obstacles to adopting open source software. Luckily, &lt;a href="https://dotnet.microsoft.com/platform/support/policy/dotnet-core"&gt;Microsoft has pledged&lt;/a&gt; to provide full support for Long Term Support (LTS) versions of .NET Core and ASP.NET Core for at least three years from the time of their release. And as all development takes place in the open, you can sometimes get answers to your questions from the general community, as well as from Microsoft directly.&lt;/p&gt;

&lt;p&gt;When deciding whether to use ASP.NET Core, you have two primary dimensions to consider: whether you’re already a .NET developer, and whether you’re creating a new application or looking to convert an existing one.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you’re new to .NET development
&lt;/h3&gt;

&lt;p&gt;If you’re new to .NET development and are considering ASP.NET Core, then welcome! Microsoft is pushing ASP.NET Core as an attractive option for web development beginners, but taking .NET cross-platform means it’s competing with many other frameworks on their own turf. ASP.NET Core has many selling points when compared to other cross-platform web frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s a modern, high-performance, open source web framework.&lt;/li&gt;
&lt;li&gt;It uses familiar design patterns and paradigms.&lt;/li&gt;
&lt;li&gt;C# is a great language (or you can use VB.NET or F# if you prefer).&lt;/li&gt;
&lt;li&gt;You can build and run on any platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ASP.NET Core is a re-imagining of the ASP.NET framework, built with modern software design principles on top of the new .NET Core platform. Although new in one sense, .NET Core has several years of widespread production use, and has drawn significantly from the mature, stable, and reliable .NET Framework, which has been used for nearly two decades. You can rest easy knowing that by choosing ASP.NET Core and .NET Core, you’ll be getting a dependable platform as well as a fully-featured web framework.&lt;/p&gt;

&lt;p&gt;Many of the web frameworks available today use similar, well-established design patterns, and ASP.NET Core is no different. For example, Ruby on Rails is known for its use of the Model-View-Controller (MVC) pattern; Node.js is known for the way it processes requests using small discrete modules (called a pipeline); and dependency injection is found in a wide variety of frameworks. If these techniques are familiar to you, you should find it easy to transfer them across to ASP.NET Core; if they’re new to you, then you can look forward to using industry best practices!&lt;/p&gt;

&lt;p&gt;The primary language of .NET development, and ASP.NET Core in particular, is C#. This language has a huge following, and for good reason! As an object-oriented C-based language, it provides a sense of familiarity to those used to C, Java, and many other languages. In addition, it has many powerful features, such as Language Integrated Query (LINQ), closures, and asynchronous programming constructs. The C# language is also designed in the open on GitHub, as is Microsoft’s C# compiler, codenamed Roslyn.&lt;/p&gt;

&lt;p&gt;The primary language of .NET development and ASP.NET Core is C#. This language has a huge following, and for good reason! As an object-oriented C-based language it provides a sense of familiarity to those familiar with C, Java, and many other languages. In addition, it has many powerful features, such as Language Integrated Query (LINQ), closures, and asynchronous programming constructs. The C# language is also designed in the open on GitHub, as is Microsoft’s C# compiler, code-named Roslyn &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; If you want to learn C#, I recommend &lt;a href="https://www.manning.com/books/c-sharp-in-depth-fourth-edition?utm_source=authorblog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=book_aspdotnetcoreinaction%2C2e&amp;amp;utm_content=article_01"&gt;C# in Depth, Fourth Edition&lt;/a&gt; by Jon Skeet (Manning, 2019), and &lt;a href="https://www.manning.com/books/code-like-a-pro-in-c-sharp?utm_source=authorblog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=book_aspdotnetcoreinaction%2C2e&amp;amp;utm_content=article_01"&gt;Code like a Pro in C#&lt;/a&gt;, by Jort Rodenburg (Manning, 2021).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the major selling points of ASP.NET Core and .NET Core is the ability to develop and run on any platform. Whether you’re using a Mac, Windows, or Linux, you can run the same ASP.NET Core apps and develop across multiple environments. As a Linux user, a wide range of distributions are supported (RHEL, Ubuntu, Debian, Cent-OS, Fedora, and openSUSE, to name a few), so you can be confident your operating system of choice will be a viable option. ASP.NET Core even runs on the tiny Alpine distribution, for truly compact deployments to containers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Built with containers in mind
&lt;/h4&gt;

&lt;p&gt;Traditionally, web applications were deployed directly to a server, or more recently, to a virtual machine. Virtual machines allow operating systems to be installed in a layer of virtual hardware, abstracting away the underlying hardware. This has several advantages over direct installation, such as easy maintenance, deployment, and recovery. Unfortunately, they’re also heavy both in terms of file size and resource use.&lt;/p&gt;

&lt;p&gt;This is where containers come in. Containers are far more lightweight and don’t have the overhead of virtual machines. They’re built in a series of layers and don’t require you to boot a new operating system when starting a new one. That means they’re quick to start and are great for quick provisioning. Containers, and Docker in particular, are quickly becoming the go-to platform for building large, scalable systems.&lt;/p&gt;

&lt;p&gt;Containers have never been a particularly attractive option for ASP.NET applications, but with ASP.NET Core, .NET Core, and Docker for Windows, that’s all changing. A lightweight ASP.NET Core application running on the cross-platform .NET Core framework is perfect for thin container deployments. You can learn more about your deployment options in chapter 16.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As well as running on each platform, one of the selling points of .NET is the ability to write and compile only once. Your application is compiled to Intermediate Language (IL) code, which is a platform-independent format. If a target system has the .NET Core platform installed, then you can run compiled IL from any platform. That means you can, for example, develop on a Mac or a Windows machine and deploy the exact same files to your production Linux machines. This compile-once, run-anywhere promise has finally been realized with ASP.NET Core and .NET Core.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you’re a .NET Framework developer creating a new application
&lt;/h3&gt;

&lt;p&gt;If you’re a .NET developer, then the choice of whether to invest in ASP.NET Core for new applications has largely been a question of timing. Early versions of .NET Core were lacking in some features that made it hard to adopt. With the release of .NET Core 3.1 and .NET 5, that is no longer a problem; Microsoft now explicitly advises that all new .NET applications should use .NET Core. Microsoft has pledged to provide bug and security fixes for the older ASP.NET framework, but it won’t receive any more feature updates. .NET Framework isn’t being removed, so your old applications will continue to work, but you shouldn’t use it for new development.&lt;/p&gt;

&lt;p&gt;The main benefits of ASP.NET Core over the previous ASP.NET framework are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-platform development and deployment&lt;/li&gt;
&lt;li&gt;A focus on performance as a feature&lt;/li&gt;
&lt;li&gt;A simplified hosting model&lt;/li&gt;
&lt;li&gt;Regular releases with a shorter release cycle&lt;/li&gt;
&lt;li&gt;Open source&lt;/li&gt;
&lt;li&gt;Modular features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a .NET developer, if you aren’t using any Windows-specific constructs, such as the Registry, then the ability to build and deploy applications cross-platform opens the door to a whole new avenue of applications: take advantage of cheaper Linux VM hosting in the cloud, use Docker containers for repeatable continuous integration, or write .NET code on your Mac without needing to run a Windows virtual machine. ASP.NET Core, in combination with .NET Core, makes all this possible.&lt;/p&gt;

&lt;p&gt;.NET Core is inherently cross-platform, but you can still use platform-specific features if you need to. For example, Windows-specific features like the Registry or Directory Services can be enabled with a compatibility pack&lt;sup id="fnref2"&gt;2&lt;/sup&gt; that makes these APIs available in .NET Core. They’re only available when running .NET Core on Windows, not on Linux or macOS, so you need to take care that such applications only run in a Windows environment, or account for the potential missing APIs.&lt;/p&gt;

&lt;p&gt;The hosting model for the previous ASP.NET framework was a relatively complex one, relying on Windows IIS to provide the web server hosting. In a cross-platform environment, this kind of symbiotic relationship isn’t possible, so an alternative hosting model has been adopted, one which separates web applications from the underlying host. This opportunity has led to the development of Kestrel: a fast, cross-platform HTTP server on which ASP.NET Core can run.&lt;/p&gt;

&lt;p&gt;Instead of the previous design, whereby IIS calls into specific points of your application, ASP.NET Core applications are console applications that self-host a web server and handle requests directly, as shown in figure 3. This hosting model is conceptually much simpler and allows you to test and debug your applications from the command line, though it doesn’t remove the need to run IIS (or equivalent) in production.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iuQ-XPwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F06_Lock.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iuQ-XPwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/CH01_F06_Lock.png" alt="Figure 3"&gt;&lt;/a&gt;&lt;br&gt;
  &lt;em&gt;Figure 3. The difference between hosting models in ASP.NET (top) and ASP.NET Core (bottom). With the previous version of ASP.NET, IIS is tightly coupled with the application. The hosting model in ASP.NET Core is simpler; IIS hands off the request to a self-hosted web server in the ASP.NET Core application and receives the response, but has no deeper knowledge of the application.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Changing the hosting model to use a built-in HTTP web server has created another opportunity. Performance has been somewhat of a sore point for ASP.NET applications in the past. It’s certainly possible to build high-performing applications—Stack Overflow (&lt;a href="https://stackoverflow.com"&gt;https://stackoverflow.com&lt;/a&gt;) is testament to that—but the web framework itself isn’t designed with performance as a priority, so it can end up being somewhat of an obstacle.&lt;/p&gt;

&lt;p&gt;To be competitive cross-platform, the ASP.NET team have focused on making the Kestrel HTTP server as fast as possible. TechEmpower (&lt;a href="http://www.techempower.com/benchmarks"&gt;www.techempower.com/benchmarks&lt;/a&gt;) has been running benchmarks on a whole range of web frameworks from various languages for several years now. In Round 19 of the plain text benchmarks, TechEmpower announced that ASP.NET Core with Kestrel was the fastest of over 400 frameworks tested&lt;sup id="fnref3"&gt;3&lt;/sup&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Web servers – naming things is hard
&lt;/h4&gt;

&lt;p&gt;One of the difficult aspects of programming for the web is the confusing array of often conflicting terminology. For example, if you’ve used IIS in the past, you may have described it as a web server, or possibly a web host. Conversely, if you’ve ever built an application using Node.js, you may have also referred to that application as a web server. Alternatively, you may have called the physical machine on which your application runs a web server!&lt;/p&gt;

&lt;p&gt;Similarly, you may have built an application for the internet and called it a website or a web application, probably somewhat arbitrarily based on the level of dynamism it displayed.&lt;/p&gt;

&lt;p&gt;When I say “web server” in the context of ASP.NET Core, I am referring to the HTTP server that runs as part of your ASP.NET Core application. By default, this is the Kestrel web server, but that’s not a requirement. It would be possible to write a replacement web server and substitute it for Kestrel if you desired.&lt;/p&gt;

&lt;p&gt;The web server is responsible for receiving HTTP requests and generating responses. In the previous version of ASP.NET, IIS took this role, but in ASP.NET Core, Kestrel is the web server.&lt;/p&gt;

&lt;p&gt;I will only use the term web application to describe ASP.NET Core applications, regardless of whether they contain only static content or are completely dynamic. Either way, they’re applications that are accessed via the web, so that name seems the most appropriate!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many of the performance improvements made to Kestrel did not come from the ASP.NET team themselves, but from contributors to &lt;a href="https://github.com/dotnet/aspnetcore"&gt;the open source project on GitHub&lt;/a&gt;. Developing in the open means you typically see fixes and features make their way to production faster than you would for the previous version of ASP.NET, which was dependent on .NET Framework and Windows and, as such, had long release cycles.&lt;/p&gt;

&lt;p&gt;In contrast, .NET Core, and hence ASP.NET Core, is designed to be released in small increments. Major versions will be released &lt;a href="https://devblogs.microsoft.com/dotnet/introducing-net-5/"&gt;on a predictable cadence&lt;/a&gt;, with a new version every year, and a new Long Term Support (LTS) version released every two years. In addition, bug fixes and minor updates can be released as and when they’re needed. Additional functionality is provided as NuGet packages, independent of the underlying .NET Core platform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; NuGet is a package manager for .NET that enables importing libraries into your projects. It’s equivalent to Ruby Gems, npm for JavaScript, or Maven for Java.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To enable this, ASP.NET Core is highly modular, with as little coupling to other features as possible. This modularity lends itself to a pay-for-play approach to dependencies, where you start from a bare-bones application and only add the additional libraries you require, as opposed to the kitchen-sink approach of previous ASP.NET applications. Even MVC is an optional package! But don’t worry, this approach doesn’t mean that ASP.NET Core is lacking in features; it means you need to opt in to them. Some of the key infrastructure improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Middleware “pipeline” for defining your application’s behavior&lt;/li&gt;
&lt;li&gt;Built-in support for dependency injection&lt;/li&gt;
&lt;li&gt;Combined UI (MVC) and API (Web API) infrastructure&lt;/li&gt;
&lt;li&gt;Highly extensible configuration system&lt;/li&gt;
&lt;li&gt;Scalable for cloud platforms by default using asynchronous programming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these features was possible in the previous version of ASP.NET but required a fair amount of additional work to set up. With ASP.NET Core, they’re all there, ready, and waiting to be connected!&lt;/p&gt;

&lt;p&gt;Microsoft fully supports ASP.NET Core, so if you have a new system you want to build, then there’s no significant reason not to. The largest obstacle you’re likely to come across is when you want to use programming models that are no longer supported in ASP.NET Core, such as Web Forms or WCF server (more about that in the next section).&lt;/p&gt;

&lt;p&gt;Hopefully, this section has whetted your appetite with some of the many reasons to use ASP.NET Core for building new applications. But if you’re an existing ASP.NET developer considering whether to convert an existing ASP.NET application to ASP.NET Core, that’s another question entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting an existing ASP.NET application to ASP.NET Core
&lt;/h3&gt;

&lt;p&gt;In contrast with new applications, an existing application is presumably already providing value, so there should always be a tangible benefit to performing what may amount to a significant rewrite in converting from ASP.NET to ASP.NET Core. The advantages of adopting ASP.NET Core are much the same as for new applications: cross-platform deployment, modular features, and a focus on performance. Determining whether the benefits are sufficient will depend largely on the particulars of your application, but there are some characteristics that are clear indicators against conversion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your application uses ASP.NET Web Forms&lt;/li&gt;
&lt;li&gt;Your application is built using WCF&lt;/li&gt;
&lt;li&gt;Your application is large, with many “advanced” MVC features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have an ASP.NET Web Forms application, then attempting to convert it to ASP.NET Core isn’t advisable. Web Forms is inextricably tied to System.Web.dll, and as such will likely never be available in ASP.NET Core. Converting an application to ASP.NET Core would effectively involve rewriting the application from scratch, not only shifting frameworks but also shifting design paradigms. A better approach would be to slowly introduce Web API concepts and try to reduce the reliance on legacy Web Forms constructs such as ViewData. You can find many resources online to help you with this approach, in particular, the &lt;a href="https://www.asp.net/web-api"&gt;https://www.asp.net/web-api&lt;/a&gt; website.&lt;/p&gt;

&lt;p&gt;Windows Communication Foundation (WCF) is only partially supported in ASP.NET Core&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. It’s possible to consume some WCF services, but support is spotty at best. There’s no supported way to host a WCF service from an ASP.NET Core application, so if you absolutely must support WCF, then ASP.NET Core may be best avoided for now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt; If you like WCFs RPC-style of programming, but don’t have a hard requirement on WCF itself, then consider using gRPC instead. gRPC is a modern RPC framework with many similar concepts as WCF and is supported by ASP.NET Core out-of-the-box. You can find an eBook from Microsoft on gRPC for WCF developers at &lt;a href="https://docs.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/"&gt;https://docs.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your existing application is complex and makes extensive use of the previous MVC or Web API extensibility points or message handlers, then porting your application to ASP.NET Core may be more difficult. ASP.NET Core is built with many similar features to the previous version of ASP.NET MVC, but the underlying architecture is different. Several of the previous features don’t have direct replacements, and so will require rethinking.&lt;/p&gt;

&lt;p&gt;The larger the application, the greater the difficulty you’re likely to have converting your application to ASP.NET Core. Microsoft itself suggests that porting an application from ASP.NET MVC to ASP.NET Core is at least as big a rewrite as porting from ASP.NET Web Forms to ASP.NET MVC. If that doesn’t scare you, then nothing will!&lt;/p&gt;

&lt;p&gt;If an application is rarely used, isn’t part of your core business, or won’t need significant development in the near term, then I strongly suggest you &lt;em&gt;don’t&lt;/em&gt; try to convert it to ASP.NET Core. Microsoft will support .NET Framework for the foreseeable future (Windows itself depends on it!) and the payoff in converting these “fringe” applications is unlikely to be worth the effort.&lt;/p&gt;

&lt;p&gt;So, when &lt;em&gt;should&lt;/em&gt; you port an application to ASP.NET Core? As I’ve already mentioned, the best opportunity for getting started is on small, green-field, new projects instead of existing applications. That said, if the existing application in question is small, or will need significant future development, then porting may be a good option. It is always best to work in small iterations where possible, rather than attempting to convert the entire application at once. But if your application consists primarily of MVC or Web API controllers and associated Razor views, then moving to ASP.NET Core may well be a good choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That’s all for this article. If you want to see more of the book’s contents, you can preview them on our browser-based liveBook platform &lt;a href="https://livebook.manning.com/book/asp-net-core-in-action-second-edition?origin=product-look-inside&amp;amp;utm_source=authorblog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=book_aspdotnetcoreinaction%2C2e&amp;amp;utm_content=article_01"&gt;here&lt;/a&gt;. Don’t forget to save 40% with code &lt;strong&gt;bllock2&lt;/strong&gt; at &lt;a href="https://www.manning.com/books/asp-net-core-in-action-second-edition?a_aid=aspnetcore-in-action&amp;amp;a_bid=44c089ee"&gt;manning.com&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The C# language and .NET Compiler Platform GitHub source code repository can be found at &lt;a href="https://github.com/dotnet/roslyn"&gt;https://github.com/dotnet/roslyn&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;The Windows Compatibility Pack is designed to help port code from .NET Framework to .NET Core. See &lt;a href="http://mng.bz/50hu"&gt;http://mng.bz/50hu&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;As always in web development, technology is in a constant state of flux, so these benchmarks will evolve over time. Although ASP.NET Core may not maintain its top ten slot, you can be sure that performance is one of the key focal points of the ASP.NET Core team. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;You can find the client libraries for using WCF with .NET Core at &lt;a href="https://github.com/dotnet/wcf"&gt;https://github.com/dotnet/wcf&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>aspnetcore</category>
      <category>aspnetcoreinaction</category>
    </item>
    <item>
      <title>Adding host filtering to Kestrel in ASP.NET Core</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 16 Jun 2020 09:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/adding-host-filtering-to-kestrel-in-asp-net-core-2c1a</link>
      <guid>https://dev.to/andrewlocknet/adding-host-filtering-to-kestrel-in-asp-net-core-2c1a</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m2Rvua4a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m2Rvua4a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_banner.png" alt="Adding host filtering to Kestrel in ASP.NET Core"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Host filtering, restricting the hostnames that your app responds to, is recommended whenever you're running in production for security reasons. In this post, I describe how to add host filtering to an ASP.NET Core application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is host filtering?
&lt;/h2&gt;

&lt;p&gt;When you run an ASP.NET Core application using Kestrel, you can choose the &lt;em&gt;ports&lt;/em&gt; it binds to, &lt;a href="https://andrewlock.net/5-ways-to-set-the-urls-for-an-aspnetcore-app/"&gt;as I described in a previous post&lt;/a&gt;. You have a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bind to a port on the loopback (localhost) address&lt;/li&gt;
&lt;li&gt;Bind to a port on a specific IP address&lt;/li&gt;
&lt;li&gt;Bind to a port on any IP address on the machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that we didn't mention a hostname (e.g. example.org) at any point here - and that's because Kestrel &lt;em&gt;doesn't&lt;/em&gt; bind to a specific host name, it just listens on a given port.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys?view=aspnetcore-3.1"&gt;HTTP.sys &lt;em&gt;can&lt;/em&gt; be used to bind to a specific hostname&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DNS is used to convert the hostname you type in your address bar to an IP address, and typically port 80, or (443 for HTTPS). You can simulate configuring DNS with a provider locally be editing the &lt;em&gt;hosts&lt;/em&gt; file on your local machine, as I'll show in the remainder of this section&lt;/p&gt;

&lt;p&gt;On Linux, you can run &lt;code&gt;sudo nano /etc/hosts&lt;/code&gt; to edit the hosts file. On Windows, open an administrative command prompt, and run &lt;code&gt;notepad C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;. At the bottom of the file, add entries for &lt;code&gt;site-a.local&lt;/code&gt; and &lt;code&gt;site-b.local&lt;/code&gt; that point to your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Other existing configuration&lt;/span&gt;
&lt;span class="c"&gt;# For example:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# 102.54.94.97 rhino.acme.com # source server&lt;/span&gt;
&lt;span class="c"&gt;# 38.25.63.10 x.acme.com # x client host&lt;/span&gt;

127.0.0.1 site-a.local
127.0.0.1 site-b.local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now create a simple ASP.NET Core app, for example using &lt;code&gt;dotnet new webapi&lt;/code&gt;. By default, if you run &lt;code&gt;dotnet run&lt;/code&gt;, your application will listen on ports 5000 (HTTP) and 5001 (HTTPS), on all IP addresses. If you navigate to &lt;a href="https://localhost:5001/weatherforecast"&gt;https://localhost:5001/weatherforecast&lt;/a&gt;, you'll see the results from the standard default &lt;code&gt;WeatherForecastController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to your additions to the &lt;em&gt;hosts&lt;/em&gt; file, you can now &lt;em&gt;also&lt;/em&gt; access the site at &lt;em&gt;site-a.local&lt;/em&gt; and &lt;em&gt;site-b.local&lt;/em&gt;, for example, &lt;a href="https://site-a.local:5001/weatherforecast"&gt;https://site-a.local:5001/weatherforecast&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lkcTCRJ2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lkcTCRJ2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_01.png" alt="Image of accessing the site using the site-a.local domain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You'll need to click through some SSL warnings to view the example above, as the development SSL is only valid for localhost, not our custom domain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, there's no host filtering, so you can access the ASP.NET Core app via localhost, or any other hostname that maps to your IP address, such as our custom &lt;code&gt;site-a.local&lt;/code&gt; domain. But why does that matter?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should you use host filtering?
&lt;/h2&gt;

&lt;p&gt;The Microsoft documentation points out that you should use host filtering for a couple of reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#url-generation-concepts"&gt;When doing URL generation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-3.1#host-filtering"&gt;When hosting behind a reverse proxy and the &lt;code&gt;Host&lt;/code&gt; header is forwarded correctly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are several attacks which rely on an apps responding to requests, regardless of the host name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://benmmurphy.github.io/blog/2016/07/11/rails-webconsole-dns-rebinding/"&gt;A DNS rebinding attack&lt;/a&gt;, that allows attackers to execute code on your machine if you're running a server on localhost.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://carlos.bueno.org/2008/06/host-header-injection.html"&gt;Cache poisoning attacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html"&gt;Password reset hijacking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter two attacks essentially rely on an application "echoing" the hostname used to access the website when generating URLs.&lt;/p&gt;

&lt;p&gt;You can easily see this vulnerability in an ASP.NET Core app if you generate an absolute URL, for use in a password reset email for example. As a simple example, consider the controller below: this generates an absolute link to the WeatherForecast action (shown in the previous image):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Specifying the protocol means an absolute URL is generated instead of a relative link. There are various other methods that generate absolute links, as well &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#url-generation-concepts"&gt;as others on the &lt;code&gt;LinkGenerator&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValuesController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetPasswordReset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"WeatherForecast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Depending on the hostname you access the site with, a different link is generated. By leveraging "forgot your password" functionality, an attacker could send an email from your system to any of your users with a link to a malicious domain under the attacker's control!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1uUzBsMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1uUzBsMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_02.png" alt="Image of generating links based on the hostname"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully we can all agree that's bad… luckily the fix isn't hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling host filtering for Kestrel
&lt;/h2&gt;

&lt;p&gt;Host filtering is added by default in the &lt;code&gt;ConfigureWebHostDefaults()&lt;/code&gt; method, but it's disabled by default. If you're using this method, you can enable the middleware by setting the &lt;code&gt;"AllowedHosts"&lt;/code&gt; value in the app's &lt;code&gt;IConfiguration&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the default templates, this value is set to &lt;code&gt;*&lt;/code&gt; in &lt;em&gt;appsettings.json&lt;/em&gt;, which disables the middleware. To add host filtering, add a semicolon delimited list of hostnames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AllowedHosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"site-a.local;localhost"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can set the configuration value using any enabled configuration provider, for example using an environment variable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this value set, you can still access the allowed host names, but all other requests to other hosts will return a 400 response, stating the hostname is invalid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G1IfPqrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G1IfPqrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/host_filtering_03.png" alt="Hostnames not included in the AllowedHosts setting will return a 400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're not using the &lt;code&gt;ConfigureWebHostDefaults()&lt;/code&gt; method, you need to configure the &lt;code&gt;HostFilteringOptions&lt;/code&gt; yourself, and add the &lt;code&gt;HostFilteringMiddleware&lt;/code&gt; manually to your middleware pipeline. You can configure these in &lt;code&gt;Startup.ConfigureServices()&lt;/code&gt;. For example, the following uses the &lt;code&gt;"AllowedHosts"&lt;/code&gt; configuration setting, in a similar way to the defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ..other config&lt;/span&gt;

        &lt;span class="c1"&gt;// "AllowedHosts": "localhost;127.0.0.1;[::1]"&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AllowedHosts"&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sc"&gt;';'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HostFilteringOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowedHosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Should be first in the pipeline&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHostFiltering&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// .. other config&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is very similar to the default configuration used by &lt;code&gt;ConfigureWebHostDefaults()&lt;/code&gt;, though it doesn't allow changing the configured hosts at runtime. &lt;a href="https://github.com/dotnet/aspnetcore/blob/5f7e1a0004/src/DefaultBuilder/src/WebHost.cs"&gt;See the default implementation&lt;/a&gt; if that's something you need.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default configuration uses an &lt;code&gt;IStartupFilter&lt;/code&gt;, &lt;code&gt;HostFilteringStartupFilter&lt;/code&gt;, to add the hosting middleware, but it's &lt;code&gt;internal&lt;/code&gt;, so you'll have to make do with the approach above.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Host filtering is especially important when you're running Kestrel on the edge, without a reverse proxy, as in most cases a reverse proxy will manage the host filtering for you. Depending on the reverse proxy you use, you may need to set the &lt;code&gt;ForwardedHeadersOptions.AllowedHosts&lt;/code&gt; value, to restrict the allowed values of the &lt;code&gt;X-Forwarded-Host&lt;/code&gt; header. You can read more about configuring the forwarded headers and a reverse proxy &lt;a href="https://docs.microsoft.com/aspnet/core/host-and-deploy/proxy-load-balancer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I described Kestrel's default behaviour, of binding to a &lt;em&gt;port&lt;/em&gt; not a domain. I then showed how this behaviour can be used as an attack vector by generating malicious links, if you don't filter requests to only a limited number of hosts. Finally, I showed how to enable host filtering by setting the &lt;code&gt;AllowedHosts&lt;/code&gt; value in configuration, or by manually adding the &lt;code&gt;HostFilteringMiddleware&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>hosting</category>
      <category>security</category>
    </item>
    <item>
      <title>Setting global authorization policies using the DefaultPolicy and the FallbackPolicy in ASP.NET Core 3.x</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 09 Jun 2020 09:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/setting-global-authorization-policies-using-the-defaultpolicy-and-the-fallbackpolicy-in-asp-net-core-3-x-2l5k</link>
      <guid>https://dev.to/andrewlocknet/setting-global-authorization-policies-using-the-defaultpolicy-and-the-fallbackpolicy-in-asp-net-core-3-x-2l5k</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6pJBrnMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/global_authorization.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6pJBrnMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/global_authorization.jpg" alt="Setting global authorization policies using the DefaultPolicy and the FallbackPolicy in ASP.NET Core 3.x"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ASP.NET Core has an extensive authorization system that you can use to create complex authorization policies. In this post, I look at the various ways you can apply these policies to large sections of your application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://andrewlock.net/custom-authorisation-policies-and-requirements-in-asp-net-core/"&gt;I wrote about creating custom authorization policies several years ago&lt;/a&gt;. For a more up-to-date look, &lt;a href="https://www.manning.com/books/asp-net-core-in-action-second-edition?a_aid=aspnetcore-in-action&amp;amp;a_bid=44c089ee"&gt;my new book ASP.NET Core in Action is currently in pre-release&lt;/a&gt;. Use code &lt;strong&gt;mllock2&lt;/strong&gt; to get 50% off until June 10th 2020!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll start by configuring a global &lt;code&gt;AuthorizeFilter&lt;/code&gt; and see why that's no longer the recommended approach in ASP.NET Core 3.0+. We'll then look at the alternative, using endpoint routing, as well as using Razor Page conventions to apply different authorization policies to different parts of your app. We'll also compare the &lt;em&gt;DefaultPolicy&lt;/em&gt; to the &lt;em&gt;FallbackPolicy&lt;/em&gt; see when each of them are applied, and how you can update them.&lt;/p&gt;

&lt;p&gt;For the purposes of this post, I'll assume you have a standard Razor Pages application, with a &lt;em&gt;Startup.cs&lt;/em&gt; something like the following. The details of this aren't very important, I just assume you have already configured authentication and a UI system for your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configure ASP.NET Core Identity + EF Core&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DefaultConnection"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDefaultIdentity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IdentityUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEntityFrameworkStores&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Add Razor Pages services&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Add base authorization services&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Ensure the following middleware are in the order shown&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Add Razor Pages to the application&lt;/span&gt;
            &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point, you have authentication, and you want to start protecting your application. You could apply &lt;code&gt;[Authorize]&lt;/code&gt; attributes to every Razor Page, but you want to be a bit safer, and apply authorization globally, to all the pages in your application. For the rest of this post, we look at the various options available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Globally applying an AuthorizeFilter
&lt;/h2&gt;

&lt;p&gt;The first option is to apply an &lt;code&gt;AuthorizeFilter&lt;/code&gt; globally to all your MVC actions and Razor Pages. This is the approach traditionally used in earlier versions of ASP.NET Core.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that &lt;a href="https://docs.microsoft.com/aspnet/core/migration/22-to-30#authorization"&gt;this is not the recommend approach to apply authorization globally in ASP.NET Core 3.0+&lt;/a&gt;. You'll see other approaches later in the post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, you can add an &lt;code&gt;AuthorizeFilter&lt;/code&gt; to all your Razor Page actions when configuring your Razor Pages in &lt;code&gt;ConfigureServices&lt;/code&gt; (you can configure MVC controllers in a similar way):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;

    &lt;span class="c1"&gt;// Add a default AuthorizeFilter to all endpoints&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMvcOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthorizeFilter&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is equivalent to decorating all your Razor Pages with an &lt;code&gt;[Authorize]&lt;/code&gt; attribute, so users are authorized using the &lt;code&gt;DefaultPolicy&lt;/code&gt; (more on that shortly!), which by default just requires an authenticated user. If you're not authenticated, you'll be redirected to the login page for Razor Pages apps (you'll receive a &lt;code&gt;401&lt;/code&gt; response for APIs).&lt;/p&gt;

&lt;p&gt;If you want to apply a different policy, you can specify one in the constructor of the &lt;code&gt;AuthorizeFilter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;

    &lt;span class="c1"&gt;// Pass a policy in the constructor of the Authorization filter&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMvcOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthorizeFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyCustomPolicy"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="c1"&gt;// Configure the custom policy&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyCustomPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;policyBuilder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;policyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SomeClaim"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The authorization filter is still applied globally, so users will always be required to login, but now they must also satisfy the &lt;code&gt;"MyCustomPolicy"&lt;/code&gt; policy. If they don't, they'll be redirected to an access denied page for Razor Pages apps (or receive a &lt;code&gt;403&lt;/code&gt; for APIs).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember, this policy applies &lt;em&gt;globally&lt;/em&gt; so you need to ensure your "Login" and "AccessDenied" pages are decorated with &lt;code&gt;[AllowAnonymous]&lt;/code&gt;, otherwise you'll end up with endless redirects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Applying &lt;code&gt;AuthorizeFilter&lt;/code&gt;s like this was the standard approach for early versions of ASP.NET Core, but ASP.NET Core 3.0 introduced &lt;a href="https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/#the-evolution-of-routing"&gt;endpoint routing&lt;/a&gt;. Endpoint routing allows moving some previously-MVC-only features to being first-class citizens. Authorization is one of those features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using RequireAuthorization on endpoint definitions
&lt;/h2&gt;

&lt;p&gt;The big problem with the &lt;code&gt;AuthorizeFilter&lt;/code&gt; approach is that it's an MVC-only feature. ASP.NET Core 3.0+ provides a different mechanism for setting authorization on endpoints—the &lt;code&gt;RequireAuthorization()&lt;/code&gt; extension method on &lt;code&gt;IEndpointConventionBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of configuring a global &lt;code&gt;AuthorizeFilter&lt;/code&gt;, call &lt;code&gt;RequireAuthorization()&lt;/code&gt; when configuring the endpoints of your application, in &lt;code&gt;Configure()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;

    &lt;span class="c1"&gt;// No need to add extra filters&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Default authorization services&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Require Authorization for all your Razor Pages&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The net effect of this is the same as applying a global &lt;code&gt;AuthorizeFilter&lt;/code&gt;. So why use this approach? One big advantage is the ability to add authorization for &lt;em&gt;other&lt;/em&gt; endpoints, that aren't MVC or Razor Pages. For example, you could require authenticated requests for your health check endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Require Authorization for all your Razor Pages&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Also require authorization for your health check endpoints&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As before, you can specify a different policy to apply in the call to &lt;code&gt;RequireAuthorization()&lt;/code&gt;. You could also provide different policies to apply for different endpoints. In the example below I'm applying the &lt;code&gt;"MyCustomPolicy"&lt;/code&gt; policy to the Razor Pages endpoints, and two policies, &lt;code&gt;"OtherPolicy"&lt;/code&gt; and &lt;code&gt;"MainPolicy"&lt;/code&gt; to the health check endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Require Authorization for all your Razor Pages&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyCustomPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Also require authorization for your health check endpoints&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OtherPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MainPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As always, ensure you've registered the policies in the call to &lt;code&gt;AddAuthorization()&lt;/code&gt;, and ensure your added &lt;code&gt;[AllowAnonymous]&lt;/code&gt; to your Login Access Denied pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you don't provide a policy name in the &lt;code&gt;RequireAuthorization()&lt;/code&gt; call, then the &lt;em&gt;DefaultPolicy&lt;/em&gt; is applied. This is the same behaviour as using an &lt;code&gt;[Authorize]&lt;/code&gt; filter without a policy name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing the &lt;code&gt;DefaultPolicy&lt;/code&gt; for an application
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;DefaultPolicy&lt;/em&gt; is the policy that is applied when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You specify that authorization is required, either using &lt;code&gt;RequireAuthorization()&lt;/code&gt;, by applying an &lt;code&gt;AuthorizeFilter&lt;/code&gt;, or by using the &lt;code&gt;[Authorize]&lt;/code&gt; attribute on your actions/Razor Pages.&lt;/li&gt;
&lt;li&gt;You don't specify which policy to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out-of-the-box, the &lt;em&gt;DefaultPolicy&lt;/em&gt; is configured as the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthorizationPolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthenticatedUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That means if you're authenticated, then you're authorized. This provides the default behaviour that you're likely familiar with, of redirecting unauthenticated users to the login page, but allowing any authenticated user access to the page.&lt;/p&gt;

&lt;p&gt;You can change the &lt;em&gt;DefaultPolicy&lt;/em&gt; so that an empty &lt;code&gt;[Authorize]&lt;/code&gt; attribute applies a different policy in &lt;code&gt;UseAuthorization()&lt;/code&gt;. For example, the following sets the &lt;em&gt;DefaultPolicy&lt;/em&gt; to a policy that requires users have the Claim &lt;code&gt;"SomeClaim"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configure the default policy&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthorizationPolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SomeClaim"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// ...other policy configuration&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// The default policy applies here, as no other policy set&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// DefaultPolicy not used, as OtherPolicy is provided&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OtherPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// DefaultPolicy not applied, as authorization not required&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ready"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The example above shows when the &lt;em&gt;DefaultPolicy&lt;/em&gt; is applied, and when it isn't. The &lt;em&gt;DefaultPolicy&lt;/em&gt; only applies when you've request authorization &lt;em&gt;and&lt;/em&gt; you haven't specified a different policy. So it only applies to the Razor Pages endpoints in the example above.&lt;/p&gt;

&lt;p&gt;Applying a default policy like this can be very useful, but sometimes you want to have slightly more granular control over when to apply policies. In Razor Pages applications for example, you might want to apply a given policy to one folder, and a different policy to another folder or area. You can achieve that using Razor Pages conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying authorization policies using conventions with Razor Pages
&lt;/h2&gt;

&lt;p&gt;The Razor Pages framework is designed around a whole set of conventions that are designed to make it easy to quickly build applications. However, you can customise virtually all of those conventions when your app starts, and authorization is no different.&lt;/p&gt;

&lt;p&gt;The Razor Page conventions allow you to set authorization requirements based on a folder, area, or page. They also allow you to mark sections and pages with &lt;code&gt;AllowAnonymous&lt;/code&gt; in situations where you need to "punch a hole" through the default authorization policy. &lt;a href="https://docs.microsoft.com/aspnet/core/security/authorization/razor-pages-authorization"&gt;The documentation on this feature is excellent&lt;/a&gt;, so I've just provided a brief example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;

    &lt;span class="c1"&gt;// Applying multiple conventions &lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// These apply authorization policies to various folders and pages&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conventions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthorizeAreaFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Accounts"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conventions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthorizePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ChangePassword"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// You can provide the policy as an optional parameter, otherwise the DefaultPolicy is used&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conventions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthorizeFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/Management"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MyCustomPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

        &lt;span class="c1"&gt;// You can also configure [AllowAnonymous] for pages/folders/areas&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conventions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnonymousToAreaPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Identity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Account/AccessDenied"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...other policy configuration&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These conventions can be useful for broadly applying authorization policies to whole sections of your application. But what if you just want to apply authorization &lt;em&gt;everywhere&lt;/em&gt;? That's where the &lt;em&gt;FallbackPolicy&lt;/em&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the FallbackPolicy to authorize everything
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;FallbackPolicy&lt;/em&gt; is applied when the following is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint does &lt;em&gt;not&lt;/em&gt; have any authorisation applied. No &lt;code&gt;[Authorize]&lt;/code&gt; attribute, no &lt;code&gt;RequireAuthorization&lt;/code&gt;, nothing.&lt;/li&gt;
&lt;li&gt;The endpoint does &lt;em&gt;not&lt;/em&gt; have an &lt;code&gt;[AllowAnonymous]&lt;/code&gt; applied, either explicitly or using conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the &lt;code&gt;FallbackPolicy&lt;/code&gt; only applies if you don't apply &lt;em&gt;any&lt;/em&gt; other sort of authorization policy, including the &lt;code&gt;DefaultPolicy&lt;/code&gt;, When that's true, the &lt;em&gt;FallbackPolicy&lt;/em&gt; is used.&lt;/p&gt;

&lt;p&gt;By default, the &lt;em&gt;FallbackPolicy&lt;/em&gt; is a no-op; it allows all requests &lt;em&gt;without&lt;/em&gt; authorization. You can change the &lt;em&gt;FallbackPolicy&lt;/em&gt; in the same way as the &lt;em&gt;DefaultPolicy&lt;/em&gt;, in &lt;code&gt;UseAuthorization&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...other config as before&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configure the default policy&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FallbackPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthorizationPolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SomeClaim"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// ...other policy configuration&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// The FallbackPolicy applies here, as no other policy set&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRazorPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// FallbackPolicy not used, as authorization applied&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/healthz"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OtherPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// FallbackPolicy not used, as DefaultPolicy applied&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapHealthChecks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ready"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the example above, the &lt;em&gt;FallbackPolicy&lt;/em&gt; is set to a custom policy. It only applies to the Razor Pages endpoints, as the health check endpoints have specified authorization requirements of &lt;code&gt;"OtherPolicy"&lt;/code&gt; and the &lt;em&gt;DefaultPolicy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;With the combination of the &lt;em&gt;DefaultPolicy&lt;/em&gt;, &lt;em&gt;FallbackPolicy&lt;/em&gt;, Razor Page conventions, and the &lt;code&gt;RequireAuthorization()&lt;/code&gt; extension method, you have multiple ways to apply authorization "globally" in your application. Remember that you can always override the &lt;em&gt;DefaultPolicy&lt;/em&gt; and &lt;em&gt;FallbackPolicy&lt;/em&gt; to achieve a more specific behaviour by applying an &lt;code&gt;[Authorize]&lt;/code&gt; or &lt;code&gt;[AllowAnonymous]&lt;/code&gt; attribute directly to a Razor Page or action method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I described the various options available for setting global authorization policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply an &lt;code&gt;AuthorizeFilter&lt;/code&gt; globally. This is no longer the recommended approach, as it is limited to MVC/Razor Pages endpoints.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;RequireAuthorization()&lt;/code&gt; when configuring an endpoint in &lt;code&gt;UseEndpoints()&lt;/code&gt;. You can specify a policy to apply, or leave blank to apply the &lt;em&gt;DefaultPolicy&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Apply authorization to Razor Pages using conventions when you call &lt;code&gt;AddRazorPages()&lt;/code&gt;. You can apply authorization policies &lt;em&gt;and&lt;/em&gt; &lt;code&gt;[AllowAnonymous]&lt;/code&gt; using these conventions.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;DefaultPolicy&lt;/em&gt; is applied when you specify that authorization is required, but don't specify a policy to apply. By default, the &lt;em&gt;DefaultPolicy&lt;/em&gt; authorizes all authenticated users.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;FallbackPolicy&lt;/em&gt; is applied when no authorization requirements are specified, including the &lt;code&gt;[Authorize]&lt;/code&gt; attribute or equivalent. By default, the &lt;em&gt;FallbackPolicy&lt;/em&gt; does not apply any authorization.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aspnetcore</category>
      <category>aspnetcoreidentity</category>
      <category>auth</category>
    </item>
    <item>
      <title>Customising the ASP.NET Core default UI without editing the PageModels</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 02 Jun 2020 09:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/customising-the-asp-net-core-default-ui-without-editing-the-pagemodels-3gd1</link>
      <guid>https://dev.to/andrewlocknet/customising-the-asp-net-core-default-ui-without-editing-the-pagemodels-3gd1</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---TCF5pAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---TCF5pAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_banner.png" alt="Customising the ASP.NET Core default UI without editing the PageModels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ASP.NET Core Identity includes a default UI as a Razor library that enables you to quickly add users to an application, without having to build all the UI yourself. The downside is that if you want to customise any of the pages associated with the default UI, then you end up taking ownership of all the logic too. Even if all you want to do is add a CSS class to an element, you're stuck maintaining the underlying page handler logic too.&lt;/p&gt;

&lt;p&gt;In this post I show how you can replace the Razor views for the default UI, without taking ownership of the business logic stored in the Razor Page &lt;code&gt;PageModel&lt;/code&gt; code-behind files. I show how you can use the ASP.NET Core Identity scaffolder to generate the replacement Razor Pages initially, but customise these to use the &lt;em&gt;existing&lt;/em&gt;, default, PageModels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: ASP.NET Core Identity
&lt;/h2&gt;

&lt;p&gt;ASP.NET Core Identity is a series of services that provide functionality for managing and signing in users. You can use the Identity services to (among other things):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create users, and provide sign-in functionality&lt;/li&gt;
&lt;li&gt;Secure passwords using best practice, strong, hashing algorithms&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-gb/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1#require-email-confirmation"&gt;Generate short-lived tokens for email confirmation&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-3.1"&gt;multi-factor authentication&lt;/a&gt;,
&lt;/li&gt;
&lt;li&gt;Enable &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?view=aspnetcore-3.1#lockout"&gt;auto-lockout&lt;/a&gt; of users to prevent brute-force attacks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-3.1"&gt;Allow logging in with third-party providers&lt;/a&gt; like Google and Facebook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Identity services provide APIs for achieving all these things, but you still have to arrange them all in the right order. You also have to write the UI that users use to interact with the services. Obviously, that's a &lt;em&gt;huge&lt;/em&gt; investment, and is working with sensitive data, so you have to be very careful not to introduce any security holes.&lt;/p&gt;

&lt;p&gt;Prior to ASP.NET Core 2.1, your best bet for implementing this was to use the UI generated from the Visual Studio templates. Unfortunately, using templates means that your UI is fine initially, but you then have a &lt;em&gt;lot&lt;/em&gt; of code to maintain. If a bug is found in the templates, you have to go and update it yourself. What are the chances of people doing that? Slim to none I'd wager.&lt;/p&gt;

&lt;p&gt;Luckily, ASP.NET Core 2.1 introduced a default UI Razor Class Library that meant you could benefit from the same UI, without having dozens of Razor Pages in your application to maintain. If a bug is found in the UI, the NuGet package can be updated, and you seamlessly get the bug fix, and all is great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customising the default UI
&lt;/h2&gt;

&lt;p&gt;Of course, using the default UI means: you have to use the default UI. I think it's generally unlikely that users will want to use the default UI in its entirety, unless you're building internal apps only, or creating a "throwaway" app. For a start, the login and register pages include references to developer documentation that most people will want to remove:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--leqm-ziJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--leqm-ziJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_01.png" alt="Image of the register page showing links to developer documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though the UI is contained in a Razor Class Library, you can "overwrite" individual pages, by placing your own Razor Pages in a "magic" location in your project. For example, to override the register page, you can create a Razor Page at &lt;em&gt;Areas/Identity/Pages/Register.cshtml&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--48rNmzlU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--48rNmzlU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_02.png" alt="Image of overriding the Register Razor page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A valid concern would be "how do I know which pages I can override?". Luckily there's a .NET Core tool you can use to scaffold pages from Identity in the correct locations, along with supporting files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffolding Identity files with the .NET CLI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/aspnet/core/security/authentication/scaffold-identity"&gt;The documentation for scaffolding Identity pages is excellent&lt;/a&gt;, so I'll just run through the basics with the .NET CLI here. You can also use Visual Studio, but be sure to follow steps 1-3 below, otherwise you get weird random errors when running the scaffolder.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add all the required packages to your application. If you're already using EF Core in your app, then you may already have some of these, but make sure they're all there, as missing packages can cause frustrating errors locally
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Also make sure that the installed package versions match your project version, for example .NET Core 3.1 projects should use packages starting 3.1.x.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Confirm your project builds without errors. If it doesn't you'll get errors when scaffolding files.&lt;/li&gt;
&lt;li&gt;Install the code generator tool globally using &lt;code&gt;dotnet tool install -g dotnet-aspnet-codegenerator&lt;/code&gt;. Alternatively, you could &lt;a href="https://andrewlock.net/new-in-net-core-3-local-tools/"&gt;install it as a local tool instead&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dotnet aspnet-codegenerator identity -lf&lt;/code&gt; from the &lt;em&gt;project&lt;/em&gt; folder (not the solution folder), to see the list of files you can scaffold:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dotnet aspnet-codegenerator identity &lt;span class="nt"&gt;-lf&lt;/span&gt;
Building project ...
Finding the generator &lt;span class="s1"&gt;'identity'&lt;/span&gt;...
Running the generator &lt;span class="s1"&gt;'identity'&lt;/span&gt;...
File List:
Account._StatusMessage
Account.AccessDenied
Account.ConfirmEmail
Account.ConfirmEmailChange
Account.ExternalLogin
Account.ForgotPassword
Account.ForgotPasswordConfirmation
Account.Lockout
... 25 more not shown!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this case, I'm going to scaffold the Account.Register page, and remove the external login provider section completely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can create a Razor Pages app using the default UI by running &lt;code&gt;dotnet new webapp -au Individual -uld&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're scaffolding into a project that's configured to use the default UI, you will already have an EF Core &lt;code&gt;IdentityDbContext&lt;/code&gt; in your application. Pass the fully namespaced name of the context in the following command, using the &lt;code&gt;-dc&lt;/code&gt; switch, when scaffolding your files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet aspnet-codegenerator identity &lt;span class="nt"&gt;-dc&lt;/span&gt; TestApp.Data.ApplicationDbContext &lt;span class="nt"&gt;--files&lt;/span&gt; &lt;span class="s2"&gt;"Account.Register"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After running this command, you'll find a bunch more files in the &lt;em&gt;Areas/Identity&lt;/em&gt; folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YDCMMg-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YDCMMg-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_03.png" alt="Scaffolded pages after running the Identity scaffolder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generated pages override the equivalents in the default UI package, so any changes you make to &lt;em&gt;Register.cshtml&lt;/em&gt; will be reflected in your app. For example, I can delete the external login provider section entirely:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WlRhz56M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WlRhz56M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_04.png" alt="Image of the Register page deleted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside is that I'm now maintaining the code-behind file &lt;em&gt;Register.cshtml.cs&lt;/em&gt;. That's 100 lines of code I'd rather not be maintaining, as I haven't changed it from the default…&lt;/p&gt;

&lt;h2&gt;
  
  
  Remove your liabilities - deleting the scaffolded PageModel
&lt;/h2&gt;

&lt;p&gt;I don't want that code, so I'm just going to delete it! As I'm only going to make changes to the Razor views, I can delete the following files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Areas/Identity/Pages/Account/Register.cshtml.cs&lt;/em&gt; — this is the &lt;code&gt;PageModel&lt;/code&gt; implementation I don't want to have to maintain&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Areas/Identity/Pages/Account/ViewImports.cshtml&lt;/em&gt; — No longer necessary, as there's nothing in the namespace it specifies now&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Areas/Identity/Pages/_ValidationScriptsPartial.cshtml&lt;/em&gt; — A duplicate of the version included in the default UI. No need to override it&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Areas/Identity/Pages/IdentityHostingStartup.cs&lt;/em&gt; — Doesn't actually configure anything, so can be deleted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, you can update &lt;em&gt;Areas/Identity/Pages/ViewImports.cshtml&lt;/em&gt; to remove the project-specific namespaces, to leave just the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@using&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;
&lt;span class="n"&gt;@addTagHelper&lt;/span&gt; &lt;span class="p"&gt;*,&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mvc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TagHelpers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point, your app won't compile. The &lt;em&gt;Register.cshtml&lt;/em&gt; page will complain that you've specified a now non-existent &lt;code&gt;RegisterModel&lt;/code&gt; as the &lt;code&gt;PageModel&lt;/code&gt; for the Razor Page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q5KbsRiW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q5KbsRiW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_05.png" alt="Image of the build error in the Register.cshtml Razor Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final step is to update the &lt;code&gt;@page&lt;/code&gt; directive to point to the &lt;em&gt;original&lt;/em&gt; &lt;code&gt;RegisterModel&lt;/code&gt; that's included with the default Identity UI, referenced in full in the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt;
&lt;span class="n"&gt;@model&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterModel&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ViewData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Register"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is the magic step. Your application will now compile, use your custom Razor views, but use the &lt;em&gt;original&lt;/em&gt; Razor Pages &lt;code&gt;PageModel&lt;/code&gt;s that are part of the default UI! That's much less code to maintain, and less chance to screw something up in your Identity pages&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FtAE58Je--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FtAE58Je--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/replace_ui_06.png" alt="Image of the files remaining after deleting extra files"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the downsides?
&lt;/h2&gt;

&lt;p&gt;So what are the downsides of this approach? The only one I can really think of is that you're very tightly tied to the &lt;code&gt;PageModel&lt;/code&gt;s in the original Identity UI, so you have to be sure that any updates that are made to the Identity UI are reflected in your Razor Page templates as appropriate. The big advantage is that if the default UI package is updated and it doesn't make any breaking changes to the Razor templates, then you get the updates with no friction at all.&lt;/p&gt;

&lt;p&gt;Another danger is that the inability to customise the &lt;code&gt;PageModel&lt;/code&gt; &lt;em&gt;may&lt;/em&gt; encourage you to do slightly janky things like &lt;code&gt;@inject&lt;/code&gt;-ing services into the Razor views that shouldn't be there, and adding additional logic into the Razor views. I'm not suggesting you should do this. If you _do_ need to change the behaviour of the page handlers, then you should just go ahead and take ownership of that code. The point is that this technique is useful when you &lt;em&gt;don't&lt;/em&gt; need to change the page handler logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I gave some background on ASP.NET Core Identity and the default UI Razor Class Library that provides over 30 Razor Pages "for free". I then showed how you could use the scaffolder tool to override one of these pages when you want to change the Razor template.&lt;/p&gt;

&lt;p&gt;The downside of this default approach is that you now have to maintain the page handlers for the scaffolded pages. That's 100s of lines of code per page that you must keep up to date when a new package version is released.&lt;/p&gt;

&lt;p&gt;I showed how you can avoid that burden by deleting the scaffolded &lt;code&gt;PageModel&lt;/code&gt; file, and point your Razor template to the &lt;em&gt;original&lt;/em&gt; &lt;code&gt;PageModel&lt;/code&gt; that comes as part of the default UI. This lets you update your Razor templates without having to take ownership of the page handler logic, potentially giving you the best of both worlds.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>aspnetcoreidentity</category>
      <category>auth</category>
    </item>
    <item>
      <title>Strongly-typed ID update 0.2.1: Using strongly-typed entity IDs to avoid primitive obsession - Part 6</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 26 May 2020 09:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/strongly-typed-id-update-0-2-1-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-6-255d</link>
      <guid>https://dev.to/andrewlocknet/strongly-typed-id-update-0-2-1-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-6-255d</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eWbxETfQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2019/obsession5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eWbxETfQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2019/obsession5.png" alt="Strongly-typed ID update 0.2.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last year I &lt;a href="https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/"&gt;wrote a series about using strongly typed IDs&lt;/a&gt; to avoid a whole class of bugs in C# applications. In this post I describe some recent updates to &lt;a href="https://github.com/andrewlock/StronglyTypedId"&gt;a NuGet package that drastically reduces the amount of boilerplate you have to write&lt;/a&gt; by auto-generating it for you at compile-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;If you don't know what strongly typed IDs are about, I suggest reading the previous posts in &lt;a href="https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/"&gt;this series&lt;/a&gt;. In summary, strongly-typed IDs help avoid bugs introduced by using primitive types for entity identifiers. For example, imagine you have &lt;a href="https://andrewlock.net/using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-1/#an-example-of-the-problem"&gt;a method signature like the following&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;GetOrderForUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Can you spot the bug in the method call?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;GetOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrderForUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The call above accidentally inverts the order of &lt;code&gt;orderId&lt;/code&gt; and &lt;code&gt;userId&lt;/code&gt; when calling the method. Unfortunately, the type system doesn't help us here because both IDs are using the same type, &lt;code&gt;Guid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Strongly Typed IDs allow you to avoid these types of bugs entirely, by using different types for the entity IDs, and using the type system to best effect. This is something that's easy to achieve in some languages (e.g. F#), but is a bit of a mess in C# (&lt;a href="https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/"&gt;at least until we get record types in C# 9&lt;/a&gt;!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;IEquatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;==(&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;!=(&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/andrewlock/StronglyTypedId"&gt;StronglyTypedId NuGet package&lt;/a&gt; massively simplifies the amount of code you need to write to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StronglyTypedId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On top of that, the StronglyTypedId package uses Roslyn to auto generate the additional code whenever you save a file. No need for snippets, full IntelliSense, but all the benefits of strongly-typed IDs!&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
    &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x5rNZmMW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://andrewlock.net/content/images/2019/strongly_typed_id.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x5rNZmMW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://andrewlock.net/content/images/2019/strongly_typed_id.gif" alt="Generating a strongly-typed ID using the StronglyTypedId packages"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;So that's the background, now lets look at some of the updates&lt;/p&gt;
&lt;h2&gt;
  
  
  Recent updates
&lt;/h2&gt;

&lt;p&gt;These updates are primarily courtesy of &lt;a href="https://github.com/vebbo2"&gt;Bartłomiej Oryszak&lt;/a&gt; who did great work! There are primarily three updates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update to the latest version of &lt;a href="https://github.com/AArnott/CodeGeneration.Roslyn"&gt;&lt;em&gt;CodeGeneration.Roslyn&lt;/em&gt;&lt;/a&gt; to support for .NET Core 3.x&lt;/li&gt;
&lt;li&gt;Support creating JSON converters for System.Text.Json&lt;/li&gt;
&lt;li&gt;Support for using long as a backing type for the strongly typed ID&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Support for .NET Core 3.x
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;StronglyTypedId&lt;/em&gt; has now been updated to the latest version of &lt;a href="https://github.com/AArnott/CodeGeneration.Roslyn"&gt;&lt;em&gt;CodeGeneration.Roslyn&lt;/em&gt;&lt;/a&gt; to support for .NET Core 3.x. This brings updates to the Roslyn build tooling, which makes the library much easier to consume. You can add a single &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt; in your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;netcoreapp3.1&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"StronglyTypedId"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"0.2.1"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"all"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Setting &lt;code&gt;PrivateAssets=all&lt;/code&gt; prevents the &lt;em&gt;CodeGeneration.Roslyn.Attributes&lt;/em&gt; and &lt;em&gt;StronglyTypedId.Attributes&lt;/em&gt; from being published to the output. There's no harm in them being there, but they're only used at compile time!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the package added, you can now add the &lt;code&gt;[StronglyTypedId]&lt;/code&gt; to your IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StronglyTypedId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateJsonConverter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Value = "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will generate a &lt;code&gt;Guid&lt;/code&gt;-backed ID, with a &lt;code&gt;TypeConverter&lt;/code&gt;, without any JSON converters. If you &lt;em&gt;do&lt;/em&gt; want explicit JSON converters, you have another option—&lt;em&gt;System.Text.Json&lt;/em&gt; converters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Support for System.Text.Json converters
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;StronglyTypedId&lt;/em&gt; has always supported the &lt;em&gt;Newtonsoft.Json&lt;/em&gt; &lt;code&gt;JsonConverter&lt;/code&gt; but now you have another option, &lt;em&gt;System.Text.Json&lt;/em&gt;. You can generate this converter by passing an appropriate &lt;code&gt;StronglyTypedIdJsonConverter&lt;/code&gt; value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StronglyTypedId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonConverter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StronglyTypedIdJsonConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemTextJson&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This generates a converter similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json.Serialization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderIdSystemTextJsonConverter&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;IEquatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// other implementation&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderIdSystemTextJsonConverter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;Utf8JsonReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;typeToConvert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Utf8JsonWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you want to generate both a &lt;em&gt;System.Text.Json&lt;/em&gt; converter and a &lt;em&gt;Newtonsoft.Json&lt;/em&gt; converter, you can use flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StronglyTypedId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonConverter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StronglyTypedIdJsonConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemTextJson&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;StronglyTypedIdJsonConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewtonsoftJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Remember, if you generate a Newtonsoft.Json converter, you'll need to add a reference to the project file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Support for long as a backing type
&lt;/h3&gt;

&lt;p&gt;The final update is adding support for using &lt;code&gt;long&lt;/code&gt; as the backing field for your strongly typed IDs. To use &lt;code&gt;long&lt;/code&gt;, use the &lt;code&gt;StronglyTypedIdBackingType.Long&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StronglyTypedId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backingType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StronglyTypedIdBackingType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The currently supported backing fields are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Guid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;long&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future work
&lt;/h2&gt;

&lt;p&gt;C# 9 is bringing some interesting features, most notably &lt;a href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/"&gt;source generators&lt;/a&gt; and &lt;a href="https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/"&gt;record types&lt;/a&gt;. Both of these features have the potential to impact the StronglyTypedId package in different ways.&lt;/p&gt;

&lt;p&gt;Source generators are designed for exactly the sort of functionality StronglyTypedId provides - build time enhancement of existing types. From a usage point of view, as far as I can tell, converting to using source generators would provide essentially the exact same experience as you can get now with &lt;a href="https://github.com/AArnott/CodeGeneration.Roslyn"&gt;&lt;em&gt;CodeGeneration.Roslyn&lt;/em&gt;&lt;/a&gt;. For that reason, it doesn't really seem worth the effort looking into at this point, unless I've missed something!&lt;/p&gt;

&lt;p&gt;Record types on the other hand are much more interesting. Records provide &lt;em&gt;exactly&lt;/em&gt; the experience we're looking for here! With the exception of the built-in &lt;code&gt;TypeConverter&lt;/code&gt; and &lt;code&gt;JsonConverter&lt;/code&gt;s, records seem like they would give an overall better experience out of the box. So when C#9 drops, I think this library can probably be safely retired 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I described some recent enhancements to the &lt;a href="https://github.com/andrewlock/StronglyTypedId"&gt;&lt;em&gt;StronglyTypedId&lt;/em&gt;&lt;/a&gt; NuGet package, which lets you generate strongly-typed IDs at compile time. The updates simplify using the &lt;em&gt;StronglyTypedId&lt;/em&gt; package in your app by supporting .NET Core 3.x, added support for &lt;code&gt;System.Text.Json&lt;/code&gt; as a &lt;code&gt;JsonConverter&lt;/code&gt;, and using &lt;code&gt;long&lt;/code&gt; as a backing field. If you have any issues using the package, let me know &lt;a href="https://github.com/andrewlock/StronglyTypedId"&gt;in the issues on GitHub&lt;/a&gt;, or in the comments below.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
    </item>
    <item>
      <title>Handling Web API Exceptions with ProblemDetails middleware</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 19 May 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/handling-web-api-exceptions-with-problemdetails-middleware-47jb</link>
      <guid>https://dev.to/andrewlocknet/handling-web-api-exceptions-with-problemdetails-middleware-47jb</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AV3ZHuAb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_banner.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AV3ZHuAb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_banner.png" alt="Handling Web API Exceptions with ProblemDetails middleware"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this short post I describe &lt;a href="https://github.com/khellang/Middleware/blob/25eac131b2595fa72e2072c87c24763e42bd8e31/src/ProblemDetails"&gt;a handy error-handling middleware&lt;/a&gt;, created by &lt;a href="https://twitter.com/khellang"&gt;Kristian Hellang&lt;/a&gt;, that is used to return &lt;code&gt;ProblemDetails&lt;/code&gt; results when an exception occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  ProblemDetails and the [ApiController] attribute
&lt;/h2&gt;

&lt;p&gt;ASP.NET Core 2.1 introduced the &lt;code&gt;[ApiController]&lt;/code&gt; attribute which &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute"&gt;applies a number of common API-specific conventions&lt;/a&gt; to controllers. In ASP.NET Core 2.2 an extra convention was added - transforming error status codes (&amp;gt;= 400) to &lt;code&gt;ProblemDetails&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Returning a consistent type, &lt;code&gt;ProblemDetails&lt;/code&gt;, for all errors makes it much easier for consuming clients. All errors from MVC controllers, whether they're a 400 (Bad Request) or a 404 (Not Found), return a &lt;code&gt;ProblemDetails&lt;/code&gt; object:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nNerCSrj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nNerCSrj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_1.png" alt="Problem details for NotFound requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, if your application throws an exception, you don't get a &lt;code&gt;ProblemDetails&lt;/code&gt; response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TloM5pN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TloM5pN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_2.png" alt="Developer exception page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the default &lt;code&gt;webapi&lt;/code&gt; template (shown below), the developer exception page handles errors in the Development environment, producing the error above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Only add error handling in development environments&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDeveloperExceptionPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the production environment, there's no exception middleware registered so you get a "raw" 500 status code without a message body at all:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TDsGtneL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TDsGtneL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_3.png" alt="Production exception page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A better option would be to consistent, and return a &lt;code&gt;ProblemDetails&lt;/code&gt; object for exceptions too. One way to achieve this would be to create a custom error handler, &lt;a href="https://andrewlock.net/creating-a-custom-error-handler-middleware-function/"&gt;as I described in a previous post&lt;/a&gt;. A better option is to use an existing NuGet package that handles it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  ProblemDetailsMiddleware
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/khellang/Middleware/tree/master/src/ProblemDetails"&gt;ProblemDetailsMiddleware&lt;/a&gt; from &lt;a href="https://twitter.com/khellang"&gt;Kristian Hellang&lt;/a&gt; does exactly what you expect - it handles exceptions in your middleware pipeline, and converts them to &lt;code&gt;ProblemDetails&lt;/code&gt;. It has a lot of configuration options (which I'll get to later), but out of the box it does exactly what we need.&lt;/p&gt;

&lt;p&gt;Add the &lt;em&gt;Hellang.Middleware.ProblemDetails&lt;/em&gt; to your &lt;em&gt;.csproj&lt;/em&gt; file, by calling &lt;code&gt;dotnet add package Hellang.Middleware.ProblemDetails&lt;/code&gt;. The latest version at the time of writing is 5.0.0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk.Web"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;netcoreapp3.1&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Hellang.Middleware.ProblemDetails"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"5.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You need to add the required services to the DI container by calling &lt;code&gt;AddProblemDetails()&lt;/code&gt;. Add the middleware itself to the pipeline by calling &lt;code&gt;UseProblemDetails&lt;/code&gt;. You should add this early in the pipeline, to ensure it catches errors from any subsequent middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProblemDetails&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Add the required services&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseProblemDetails&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Add the middleware&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this simple addition, if you get an exception somewhere in the pipeline (in a controller for example), you'll still get a &lt;code&gt;ProblemDetails&lt;/code&gt; response. In the Development environment, the middleware includes the exception details and the Stack Trace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VCSC33yQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VCSC33yQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_4.png" alt="Developer ProblemDetails page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is more than just calling &lt;code&gt;ToString()&lt;/code&gt; on the &lt;code&gt;Exception&lt;/code&gt; though - the response even includes the line that threw the exception (&lt;code&gt;contextCode&lt;/code&gt;) and includes the source code before (&lt;code&gt;preContextCode&lt;/code&gt;) and after (&lt;code&gt;postContextCode&lt;/code&gt;) the offending lines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zq5F6IOf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zq5F6IOf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_5.png" alt="Context code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Production environment, the middleware doesn't include these details for obvious reasons, and instead returns the basic &lt;code&gt;ProblemDetails&lt;/code&gt; object only.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A36eUq3k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A36eUq3k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_6.png" alt="Production ProblemDetails page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As well as handling exceptions, the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; also catches status code errors that come from &lt;em&gt;other&lt;/em&gt; middleware too. For example, if a request doesn't match any endpoints in your application, the pipeline will return a 404. The &lt;code&gt;ApiController&lt;/code&gt; attribute won't catch that, so it &lt;em&gt;won't&lt;/em&gt; be converted to a &lt;code&gt;ProblemDetails&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Similarly, by default, if you send a &lt;code&gt;POST&lt;/code&gt; request to a &lt;code&gt;GET&lt;/code&gt; method, you'll get a &lt;code&gt;405&lt;/code&gt; response, again &lt;em&gt;without&lt;/em&gt; a method body, &lt;em&gt;even if you apply the &lt;code&gt;[ApiController]&lt;/code&gt; attribute&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2v5BCbaF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2v5BCbaF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_7.png" alt="Method not found does not cause a ProblemDetails response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; in place, you get a &lt;code&gt;ProblemDetails&lt;/code&gt; response for these error codes too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VdIfQf-A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VdIfQf-A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/problem_details_8.png" alt="With the middleware in place, method not found causes a ProblemDetails response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This behaviour gave exactly what I needed out-of-the-box, but you can also extensively customise the behaviour of the middleware if you need to. In the next section, I'll show some of these customization options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customising the middleware behaviour
&lt;/h2&gt;

&lt;p&gt;You can customise the behaviour of the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; by providing a configuration lambda for an &lt;code&gt;ProblemDetailsOptions&lt;/code&gt; instance in the &lt;code&gt;AddProblemDetails&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProblemDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// configure here&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There's lots of possible configuration settings, as shown below. Most of the configuration settings are &lt;code&gt;Func&amp;lt;&amp;gt;&lt;/code&gt; properties, that give access to the current &lt;code&gt;HttpContext&lt;/code&gt;, and let you control how the middleware behaves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProblemDetailsOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SourceCodeLineCount&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IFileProvider&lt;/span&gt; &lt;span class="n"&gt;FileProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetTraceId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;IncludeExceptionDetails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;IsProblem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MvcProblemDetails&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MapStatusCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MvcProblemDetails&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OnBeforeWriteDetails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MvcProblemDetails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShouldLogUnhandledException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For example, by default, &lt;code&gt;ExceptionDetails&lt;/code&gt; are included only for the Development environment. If you wanted to include the details in the Staging environment too, you could use something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProblemDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Control when an exception is included&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeExceptionDetails&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Fetch services from HttpContext.RequestServices&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHostEnvironment&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsStaging&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Another thing worth pointing out is that you can control when the middleware should convert non-exception responses to &lt;code&gt;ProblemDetails&lt;/code&gt;. The default configuration converts non-exception responses to &lt;code&gt;ProblemDetails&lt;/code&gt; when the following is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The status code is between 400 and 600.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Content-Length&lt;/code&gt; header is empty.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Content-Type&lt;/code&gt; header is empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I mentioned at the start of this post, the &lt;code&gt;[ApiController]&lt;/code&gt; attribute from ASP.NET Core 2.2 onwards automatically converts "raw" status code results into &lt;code&gt;ProblemDetails&lt;/code&gt; anyway. Those responses are ignored by the middleware, as the response will already have a &lt;code&gt;Content-Type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if you're &lt;em&gt;not&lt;/em&gt; using the &lt;code&gt;[ApiController]&lt;/code&gt; attribute, or are still using ASP.NET Core 2.1, then you can use the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; to automatically convert raw status code results into &lt;code&gt;ProblemDetails&lt;/code&gt;, just as you get in ASP.NET Core 2.2+.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The responses in these cases aren't &lt;em&gt;identical&lt;/em&gt;, but they're very similar. There are small differences in the values used for the &lt;code&gt;Title&lt;/code&gt; and &lt;code&gt;Type&lt;/code&gt; properties for example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another option would be to use the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; in an application that combines Razor Pages with API controllers. You could then use the &lt;code&gt;IsProblem&lt;/code&gt; function to ensure that &lt;code&gt;ProblemDetails&lt;/code&gt; are only generated for API controller endpoints.&lt;/p&gt;

&lt;p&gt;I've only touched on a couple of the customisation features, but there's lots of additional hooks you can use to control how the middleware works. I just haven't had to use them, as the defaults do exactly what I need!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I described the &lt;code&gt;ProblemDetailsMiddleware&lt;/code&gt; by &lt;a href="https://twitter.com/khellang"&gt;Kristian Hellang&lt;/a&gt;, that can be used with API projects to generate &lt;code&gt;ProblemDetails&lt;/code&gt; results for exceptions. This is a very handy library if you're building APIs, as it ensures all errors return a consistent object. The project is &lt;a href="https://github.com/khellang/Middleware/blob/25eac131b2595fa72e2072c87c24763e42bd8e31/src/ProblemDetails"&gt;open source on GitHub&lt;/a&gt;, and available &lt;a href="https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/"&gt;on NuGet&lt;/a&gt;, so check it out!&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>middleware</category>
      <category>errors</category>
    </item>
    <item>
      <title>Extending the shutdown timeout setting to ensure graceful IHostedService shutdown</title>
      <dc:creator>Andrew Lock "Sock"</dc:creator>
      <pubDate>Tue, 12 May 2020 10:00:00 +0000</pubDate>
      <link>https://dev.to/andrewlocknet/extending-the-shutdown-timeout-setting-to-ensure-graceful-ihostedservice-shutdown-4kjl</link>
      <guid>https://dev.to/andrewlocknet/extending-the-shutdown-timeout-setting-to-ensure-graceful-ihostedservice-shutdown-4kjl</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DU1jAWZ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/timeout_banner.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DU1jAWZ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrewlock.net/content/images/2020/timeout_banner.jpg" alt="Extending the shutdown timeout setting to ensure graceful IHostedService shutdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was seeing an issue recently where our application wasn't running the &lt;code&gt;StopAsync&lt;/code&gt; method in our &lt;code&gt;IHostedService&lt;/code&gt; implementations when the app was shutting down. It turns out that this was due to some services taking too long to respond to the shutdown signal. In this post I show an example of the problem, discuss why it happens, and how to avoid it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running background services with IHostedService
&lt;/h2&gt;

&lt;p&gt;ASP.NET Core 2.0 introduced &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&amp;amp;tabs=visual-studio"&gt;the &lt;code&gt;IHostedService&lt;/code&gt; interface for running background tasks&lt;/a&gt;. The &lt;a href="https://github.com/dotnet/runtime/blob/f007634b02c4484f60ead75a893eb7f89f615ccf/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostedService.cs"&gt;interface consists of two methods&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHostedService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;StartAsync&lt;/code&gt; is called when the application is starting up. In ASP.NET Core 2.x this occurs &lt;a href="https://github.com/aspnet/AspNetCore/blob/v2.1.12/src/Hosting/Hosting/src/Internal/WebHost.cs#L153"&gt;just &lt;em&gt;after&lt;/em&gt;&lt;/a&gt; the application starts handling requests, while in ASP.NET Core 3.x the hosted services are started &lt;a href="https://github.com/aspnet/AspNetCore/blob/v3.0.0-preview9.19424.4/src/Hosting/Hosting/src/Internal/WebHost.cs#L154"&gt;just &lt;em&gt;before&lt;/em&gt;&lt;/a&gt; the application starts handling requests.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StopAsync&lt;/code&gt; is called when the application receives the shut down (&lt;code&gt;SIGTERM&lt;/code&gt;) signal, for example when you push CTRL+C in the console window, or the app is stopped by the host system. This allows you to close any open connections, dispose resources, and generally clean up your class as required.&lt;/p&gt;

&lt;p&gt;In practice, there are actually some subtleties to implementing this interface that means that you typically want to derive &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&amp;amp;tabs=visual-studio#backgroundservice-base-class"&gt;from the helper class &lt;code&gt;BackgroundService&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Steve Gordon has a course on Pluralsight "&lt;a href="https://pluralsight.pxf.io/vdn6j"&gt;Building ASP.NET Core Hosted Services and .NET Core Worker Services&lt;/a&gt;" if you want to learn more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problems shutting down an &lt;code&gt;IHostedService&lt;/code&gt; implementation
&lt;/h2&gt;

&lt;p&gt;The problem I saw recently was causing an &lt;code&gt;OperationCanceledException&lt;/code&gt; to be thrown when the application was shutting down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unhandled exception. System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I traced the source of this problem to one particular &lt;code&gt;IHostedService&lt;/code&gt; implementation. We use &lt;code&gt;IHostedService&lt;/code&gt;s as the host for each of our &lt;a href="https://github.com/confluentinc/confluent-kafka-dotnet"&gt;Kafka consumers&lt;/a&gt;. The specifics of this aren't important - the key is just that shutting down the &lt;code&gt;IHostedService&lt;/code&gt; is relatively slow: it can take several seconds to cancel the subscription.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Part of the problem is the way the Kafka library (and underlying &lt;code&gt;librdkafka&lt;/code&gt; library) uses synchronous, blocking &lt;code&gt;Consume&lt;/code&gt; calls, instead of async, cancellable calls. There's not a great way around that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The easiest way to understand this issue is with an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demonstrating the problem
&lt;/h2&gt;

&lt;p&gt;The easiest way to understand the problem is to create an application containing two &lt;code&gt;IHostedService&lt;/code&gt; implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NormalHostedService&lt;/code&gt; logs when it starts up and shuts down, then returns immediately. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SlowHostedService&lt;/code&gt; logs when it starts and stops, but takes 10s to complete shutdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementations for these two classes are shown below. The &lt;code&gt;NormalHostedService&lt;/code&gt; is very simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NormalHostedService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHostedService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NormalHostedService started"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NormalHostedService stopped"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SlowHostedService&lt;/code&gt; is almost identical, but it has a &lt;code&gt;Task.Delay&lt;/code&gt; that takes 10s to simulate a slow shutdown&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlowHostedService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHostedService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SlowHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SlowHostedService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SlowHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SlowHostedService started"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SlowHostedService stopping..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="n"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SlowHostedService stopped"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;IHostedService&lt;/code&gt;s I had in practice only took 1s to shutdown, but we had many of them, so the overall effect was the same as above!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The order the services are registered in &lt;code&gt;ConfigureServices&lt;/code&gt; is important in this case - to demonstrate the issue, we need &lt;code&gt;SlowHostedService&lt;/code&gt; to be shut down &lt;em&gt;first&lt;/em&gt;. Services are shut down in reverse order, which means we need to register it &lt;em&gt;last&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SlowHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run the application, you'll see the starting logs as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;info: ExampleApp.NormalHostedService[0]
      NormalHostedService started
info: ExampleApp.SlowHostedService[0]
      SlowHostedService started
...
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;However, if you press CTRL+C to shut down the application, there's a problem. The &lt;code&gt;SlowHostedService&lt;/code&gt; completes shutting down, but then an &lt;code&gt;OperationCanceledException&lt;/code&gt; is thrown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: ExampleApp.SlowHostedService[0]
      SlowHostedService stopping...
info: ExampleApp.SlowHostedService[0]
      SlowHostedService stopped

Unhandled exception. System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.WaitForShutdownAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at ExampleApp.Program.Main(String[] args) in C:\repos\andrewlock\blog-examples\SlowShutdown\Program.cs:line 16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;NormalHostedService.StopAsync()&lt;/code&gt; method is never called. If the service needed to do some cleanup then you have a problem. For example, maybe you need to gracefully deregister the service from Consul, or unsubscribe from Kafka topics - that won't happen now.&lt;/p&gt;

&lt;p&gt;So what's going on here? Where's that timeout coming from?&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason: HostOptions.ShutDownTimeout
&lt;/h2&gt;

&lt;p&gt;You can find the problematic &lt;a href="https://github.com/dotnet/runtime/blob/db2375097e40394ef270f9e0cb56baa9ab392ad2/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs#L64-L107"&gt;code in the framework &lt;code&gt;Host&lt;/code&gt; implementation&lt;/a&gt; that runs on application shutdown. A simplified version is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HostOptions&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_hostedServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a cancellation token source that fires after ShutdownTimeout seconds&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShutdownTimeout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;linkedCts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLinkedTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Create a token, which is cancelled if the timer expires&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkedCts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Run StopAsync on each registered hosted service&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hostedService&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_hostedServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// stop calling StopAsync if timer expires&lt;/span&gt;
                &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;hostedService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// .. other stopping code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The key point here is the &lt;code&gt;CancellationTokenSource&lt;/code&gt; that is configured to fire after &lt;code&gt;HostOptions.ShutdownTimeout&lt;/code&gt;. &lt;a href="https://github.com/dotnet/runtime/blob/f007634b02c4484f60ead75a893eb7f89f615ccf/src/libraries/Microsoft.Extensions.Hosting/src/HostOptions.cs"&gt;By default, this fires after 5 seconds&lt;/a&gt;. That means hosted service shutdown is abandoned after 5s - shut down of all &lt;code&gt;IHostedService&lt;/code&gt;s has to happen within this timeout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HostOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;ShutdownTimeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On the first iteration of the &lt;code&gt;foreach&lt;/code&gt; loop, the &lt;code&gt;SlowHostedService.Stopasync()&lt;/code&gt; executes, which takes 10s to run. On the second iteration, the 5s timeout is exceeded, and so &lt;code&gt;token.ThrowIfCancellationRequested();&lt;/code&gt; throws an &lt;code&gt;OperationConcelledException&lt;/code&gt;. That exits the control flow, and &lt;code&gt;NormalHostedService.Stopasync()&lt;/code&gt; is never executed.&lt;/p&gt;

&lt;p&gt;There's a simple solution to this - increase the shutdown timeout!&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: increase the shutdown timeout
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HostOptions&lt;/code&gt; isn't explicitly configured anywhere by default, so you will need to configure it manually in your &lt;code&gt;ConfigureSerices&lt;/code&gt; method. For example, the following config increases the timeout to 15s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SlowShutdownHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Configure the shutdown to 15s&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HostOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ShutdownTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Alternatively, you can also load the timeout from configuration. For example, if you add the following to &lt;em&gt;appsettings.json&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"HostOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ShutdownTimeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:00:15"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can then bind the &lt;code&gt;HostOptions&lt;/code&gt; configuration section to the &lt;code&gt;HostOptions&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NormalHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SlowShutdownHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// bind the config to host options&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HostOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HostOptions"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This binds the serialised &lt;code&gt;TimeSpan&lt;/code&gt; value &lt;code&gt;00:00:15&lt;/code&gt; to the &lt;code&gt;HostOptions&lt;/code&gt; value and sets the timeout to 15s. With that configuration, now when we stop the application, the services all shutdown correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: SlowShutdown.SlowShutdownHostedService[0]
      SlowShutdownHostedService stopping...
info: SlowShutdown.SlowShutdownHostedService[0]
      SlowShutdownHostedService stopped
info: SlowShutdown.NormalHostedService[0]
      NormalHostedService stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your application will now wait up to 15s for all the hosted services to finish shutting down before exiting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post I discussed an issue recently where our application wasn't running the &lt;code&gt;StopAsync&lt;/code&gt; method in our &lt;code&gt;IHostedService&lt;/code&gt; implementations when the app was shutting down. This was due to some background services taking too long to respond to the shutdown signal, and exceeding the shutdown timeout. I demonstrated the problem with a single service taking 10s to shutdown, but in practice it happens whenever the &lt;em&gt;total&lt;/em&gt; shutdown time for all services exceeds the default 5s.&lt;/p&gt;

&lt;p&gt;The solution to the problem was to extend the &lt;code&gt;HostOptions.ShutdownTimeout&lt;/code&gt; configuration value to be longer than 5s, using the standard ASP.NET Core &lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt; configuration system.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>generichost</category>
      <category>hostbuilder</category>
    </item>
  </channel>
</rss>
