<?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>Forem: Anthony Simmon</title>
    <description>The latest articles on Forem by Anthony Simmon (@asimmon).</description>
    <link>https://forem.com/asimmon</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%2F887236%2Fea3fab3d-5149-4e88-b01d-16b7d87ca548.png</url>
      <title>Forem: Anthony Simmon</title>
      <link>https://forem.com/asimmon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/asimmon"/>
    <language>en</language>
    <item>
      <title>Must-have resources for new .NET Aspire developers</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Mon, 27 May 2024 11:00:00 +0000</pubDate>
      <link>https://forem.com/asimmon/must-have-resources-for-new-net-aspire-developers-3jc2</link>
      <guid>https://forem.com/asimmon/must-have-resources-for-new-net-aspire-developers-3jc2</guid>
      <description>&lt;p&gt;Six months after its first preview released during .NET Conf 2023, .NET Aspire becomes generally available (GA) at Microsoft Build 2024. This project, which aims to revolutionize the local development of distributed applications, has unfortunately been overlooked by some due to its preview status. This is good news for those who can now embark on the adventure. Here are some resources to learn how to use .NET Aspire.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET Aspire official documentation
&lt;/h2&gt;

&lt;p&gt;The best place to start learning .NET Aspire is &lt;a href="https://learn.microsoft.com/dotnet/aspire/"&gt;the official documentation&lt;/a&gt;. It includes an overview, a quickstart guide, dives into conceptual details and much more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official videos on the Microsoft YouTube channels
&lt;/h2&gt;

&lt;p&gt;Not a fan of reading? Microsoft has published videos throughout the development of .NET Aspire. You can understand what problem it tries to solve, how the .NET team arrived at this solution, how to use it, and what the vision for the future is.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aka.ms/aspire/videos"&gt;Welcome to .NET Aspire video series&lt;/a&gt;, a series of 9 videos released at the Microsoft Build 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/z1M-7Bms1Jg"&gt;Building Cloud Native apps with .NET 8 | .NET Conf 2023&lt;/a&gt;, November 15, 2023.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/live/KEcUfMbCgpA"&gt;ASP.NET Community Standup - .NET Aspire Update&lt;/a&gt;, January 23, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/live/kAF9No5KZrg"&gt;ASP.NET Community Standup - .NET Aspire in action&lt;/a&gt;, February 6, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/live/Osf7_ZxRlvw"&gt;ASP.NET Community Standup: .NET Aspire Update&lt;/a&gt;, April 16, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/uryJN7UEn4M"&gt;Deploy distributed .NET apps to the cloud with .NET Aspire and Azure Container Apps&lt;/a&gt;, April 10, 2024.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Official code samples on GitHub
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/dotnet/aspire-samples"&gt;official samples repository&lt;/a&gt; contains valuable examples and demonstrates a small portion of the possibilities offered by .NET Aspire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real-world example of a distributed application with microservices,&lt;/li&gt;
&lt;li&gt;JavaScript frontends and Node.js backends integration,&lt;/li&gt;
&lt;li&gt;Desktop apps integration,&lt;/li&gt;
&lt;li&gt;Various databases integration,&lt;/li&gt;
&lt;li&gt;Dapr integration and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Community videos
&lt;/h2&gt;

&lt;p&gt;.NET Aspire has generated a lot of enthusiasm in the community. Here are some videos from developers who have experimented with .NET Aspire and share their experiences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/DORZA_S7f9w"&gt;What Is .NET Aspire? The Insane Future of .NET!&lt;/a&gt; by Nick Chapsas, November 19, 2023.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/8aG410nmjtQ"&gt;First Look at .NET Aspire - Distributed Applications in .NET 8&lt;/a&gt; by Milan Jovanović, December 26, 2023.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/fN3ufsIF7vs"&gt;WHY and HOW to Add .NET Aspire to ANY .NET API and Web App in Minutes&lt;/a&gt; by James Montemagno, April 18, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/J02mvcEKrsI"&gt;Cloud-native apps with .NET Aspire&lt;/a&gt; by Layla Porter, November 26, 2023.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/live/dJ4uEANZIdQ"&gt;Learn C# with CSharpFritz: Introducing .NET Aspire&lt;/a&gt; by Jeff Fritz, May 8, 2024.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  David Fowler's experiments on GitHub
&lt;/h2&gt;

&lt;p&gt;Do you want deep technical content about .NET Aspire? Watch how David Fowler pushes the limits of the .NET Aspire application model in these advanced scenarios. In my opinion, some of this content should be integrated into .NET Aspire.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/AspireWithRedis"&gt;How to use .NET Aspire with Redis&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/AspireYarp"&gt;How to use YARP with .NET Aspire to route between services&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/AspireSwaggerUI"&gt;How to use .NET Aspire to show a Swagger UI for any resource that exposes an OpenAPI endpoint&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/WaitForDependenciesAspire"&gt;How to extend .NET Aspire application model to enable waiting for dependencies to be available before starting the application&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/EventGridDemo"&gt;How to use .NET Aspire to deploy Event Grid for local use then publish and subscribe events between resources&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/AspireEventHub"&gt;How to use and deploy Event Hubs, which is a resource that doesn't exist in .NET Aspire by default&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/davidfowl/AspirePulumi"&gt;A prototype using Pulumi and .NET Aspire together for local development&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My blog posts
&lt;/h2&gt;

&lt;p&gt;Since the launch of .NET Aspire, I've written several articles on the subject. Here are some of my favorites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://anthonysimmon.com/exploring-microsoft-developer-control-plane-core-dotnet-aspire-dotnet-8/"&gt;Exploring the Microsoft Developer Control Plane at the heart of the new .NET Aspire&lt;/a&gt;, November 21, 2023.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://anthonysimmon.com/dotnet-aspire-dashboard-best-tool-visualize-opentelemetry-local-dev/"&gt;.NET Aspire dashboard is the best tool to visualize your OpenTelemetry data during local development&lt;/a&gt;, March 25, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://anthonysimmon.com/dotnet-aspire-best-way-to-experiment-dapr-local-dev/"&gt;.NET Aspire is the best way to experiment with Dapr during local development&lt;/a&gt;, April 29, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://anthonysimmon.com/referencing-external-docker-containers-dotnet-aspire-custom-resources/"&gt;Referencing external Docker containers in .NET Aspire using the new custom resources API&lt;/a&gt;, April 11, 2024.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://anthonysimmon.com/running-ruby-on-rails-with-dotnet-aspire/"&gt;Running Ruby on Rails web apps with .NET Aspire&lt;/a&gt;, April 18, 2024.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More blog posts, online resources and projects
&lt;/h2&gt;

&lt;p&gt;There are many other online resources to learn .NET Aspire. Here are some of my recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-aspire-general-availability/"&gt;General Availability of .NET Aspire: Simplifying .NET Cloud-Native Development&lt;/a&gt;, by Damian Edwards.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aka.ms/aspire/learn"&gt;.NET Aspire Learn Path&lt;/a&gt;, the official collection of learning resources for .NET Aspire.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aspireify.net/"&gt;Aspireify.NET&lt;/a&gt;, a .NET Aspire content aggregator by Jeff Fritz.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devblogs.microsoft.com/dotnet/category/dotnet-aspire/"&gt;.NET Aspire announcements &amp;amp; articles on Microsoft's .NET blog&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.jetbrains.com/dotnet/2024/02/19/jetbrains-rider-and-the-net-aspire-plugin/"&gt;JetBrains Rider and the .NET Aspire Plugin&lt;/a&gt;, because not everyone uses Visual Studio.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tonybaloney.github.io/posts/using-dotnet-aspire-dashboard-for-python-opentelemetry.html"&gt;Using the Aspire Dashboard for Python OpenTelemetry tracing, metrics, and logs&lt;/a&gt; by Anthony Shaw.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/prom3theu5/aspirational-manifests#aspirate-aspir8"&gt;Aspirate&lt;/a&gt;, a tool that can generate kustomize manifests for deploying aspire apps to Kubernetes, by David Sekula. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Social networks
&lt;/h2&gt;

&lt;p&gt;If you want to stay updated with the latest news on .NET Aspire and discover what the community is building with it, I recommend following the news on X: &lt;a href="https://x.com/hashtag/aspire"&gt;#aspire&lt;/a&gt; and more recently &lt;a href="https://x.com/hashtag/dotnetaspire"&gt;#dotnetaspire&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also follow the people who work closely on .NET Aspire:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/davidfowl"&gt;David Fowler&lt;/a&gt;, Distinguished Engineer at Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/DamianEdwards"&gt;Damian Edwards&lt;/a&gt;, Principal Architect at Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/timheuer"&gt;Tim Heuer&lt;/a&gt;, Principal Product Manager Lead at Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/davidpine7"&gt;David Pine&lt;/a&gt;, Senior Content Developer at Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/jamesnk"&gt;James Newton-King&lt;/a&gt;, Principal Software Engineer at Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/eehardt"&gt;Eric Erhardt&lt;/a&gt;, Principal Software Engineer at Microsoft.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  .NET Aspire's source code
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/dotnet/aspire"&gt;.NET Aspire repository on GitHub&lt;/a&gt; is the source of truth for understanding how it works. I strongly encourage you to explore it in your browser or in your IDE via SourceLink.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cover picture, from left to right: .NET Aspire documentation, David Fowler and Damian Edwards during an ASP.NET Community Standup, Aspireify.NET and Nick Chapsas on YouTube.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
      <category>cloud</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Disabling .NET Aspire authentication to skip the login page</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Mon, 06 May 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/disabling-net-aspire-authentication-to-skip-the-login-page-105d</link>
      <guid>https://forem.com/asimmon/disabling-net-aspire-authentication-to-skip-the-login-page-105d</guid>
      <description>&lt;p&gt;Preview 6 of .NET Aspire introduced a login page to access the dashboard. Unless the dashboard is launched from Visual Studio or Visual Studio Code (with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" rel="noopener noreferrer"&gt;C# Dev Kit extension&lt;/a&gt;), the login page will appear and prompt for a token. For scenarios where the dashboard is started via &lt;code&gt;dotnet run&lt;/code&gt; or from the Docker image, the token can be retrieved from the console window that was used to start the app host. An URL also containing the token in a query string parameter &lt;code&gt;t&lt;/code&gt; allows bypassing the login page and accessing the dashboard directly. Once authenticated, a cookie is created to avoid entering the token each time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5la9r9mmpgfeej34u55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5la9r9mmpgfeej34u55.png" alt="The token appears in the console window."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This login page is particularly useful when the dashboard is accessible over the local network or internet. However, for local development scenarios, some might find this step unnecessary or bothersome. Fortunately, there are two ways to disable it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling the .NET Aspire login page through an environment variable
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;code&gt;DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS&lt;/code&gt; environment variable to disable the login page, for instance in a launch settings profile:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json.schemastore.org/launchsettings.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profiles"&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;"https"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&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;"environmentVariables"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&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;"DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&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;&amp;lt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;THIS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;LINE&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="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="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;It's also possible to use it when starting the dashboard via Docker:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 18888:18888 &lt;span class="nt"&gt;-p&lt;/span&gt; 4317:18889 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; aspire-dashboard &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'true'&lt;/span&gt; mcr.microsoft.com/dotnet/nightly/aspire-dashboard:8.0.0-preview.6


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Disabling the .NET Aspire login page programmatically
&lt;/h2&gt;

&lt;p&gt;When building the &lt;code&gt;DistributedApplication&lt;/code&gt;, you can inject a configuration value to disable the login page:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="kt"&gt;var&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;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&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="n"&gt;builder&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;AddInMemoryCollection&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;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;"AppHost:BrowserToken"&lt;/span&gt;&lt;span class="p"&gt;]&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;Note that this method does not work with &lt;code&gt;appsettings.json&lt;/code&gt; files because the &lt;code&gt;DistributedApplication.CreateBuilder&lt;/code&gt; method overrides the empty token. With &lt;code&gt;AddInMemoryCollection&lt;/code&gt;, you ensure that the configuration is modified after this logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/explore#dashboard-authentication" rel="noopener noreferrer"&gt;.NET Aspire dashboard authentication documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/search?q=repo%3Adotnet%2Faspire+%22AppHost%3ABrowserToken%22&amp;amp;type=code" rel="noopener noreferrer"&gt;.NET Aspire code search for &lt;code&gt;AppHost:BrowserToken&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>.NET Aspire is the best way to experiment with Dapr during local development</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Sat, 04 May 2024 23:14:29 +0000</pubDate>
      <link>https://forem.com/asimmon/net-aspire-is-the-best-way-to-experiment-with-dapr-during-local-development-400e</link>
      <guid>https://forem.com/asimmon/net-aspire-is-the-best-way-to-experiment-with-dapr-during-local-development-400e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Not interested in reading the full article? Check out the &lt;a href="https://github.com/asimmon/aspire-dapr-demo"&gt;complete code sample on GitHub&lt;/a&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dapr.io/"&gt;Dapr&lt;/a&gt; provides a set of building blocks that abstract concepts commonly used in &lt;strong&gt;distributed systems&lt;/strong&gt;. This includes secured synchronous and asynchronous communication between services, caching, workflows, resiliency, secret management and much more. Not having to implement these features yourself &lt;strong&gt;eliminates boilerplate&lt;/strong&gt;, &lt;strong&gt;reduce complexity&lt;/strong&gt; and allows you to &lt;strong&gt;focus on developing your business features&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You might be curious to see if Dapr can help you. Perhaps out of simple curiosity, because managing your services has become complex, or because you were asked to perform a proof of concept. Regardless of the reason, &lt;strong&gt;you have decided to learn and try Dapr&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You begin by understanding the basic concepts, perhaps even watching some video tutorials or online courses. You install the CLI and the required dependencies, hoping to quickly integrate the SDK into your applications. It takes you some time to understand how to set everything up. Before you know it, you've spent most of the day focusing on YAML-based infrastructure configuration.&lt;/p&gt;

&lt;p&gt;Don't get me wrong, if you end up using Dapr in production, you will need to go through this learning process. However, in a situation where your time is limited and you just want to experiment, it can be frustrating to spend so much time on YAML configuration. Not to mention that you haven't yet determined the impact on the local development experience (troubleshooting, debugging, onboarding, etc.). Maybe some of your colleagues will initially be reluctant and believe that you're making their work more complicated than it already is.&lt;/p&gt;

&lt;p&gt;Which option &lt;strong&gt;will offer you the best local development experience&lt;/strong&gt;? Will it be the &lt;a href="https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-template/"&gt;Multi-Run template&lt;/a&gt;? &lt;a href="https://docs.dapr.io/operations/hosting/self-hosted/self-hosted-with-docker/#run-using-docker-compose"&gt;Dapr with Docker Compose&lt;/a&gt;? Or &lt;a href="https://docs.dapr.io/operations/hosting/kubernetes/kubernetes-deploy/"&gt;Dapr with Kubernetes&lt;/a&gt; via Minikube, Kind, Docker Desktop, or Helm?&lt;/p&gt;

&lt;p&gt;In this article, I'll show you &lt;strong&gt;how to use Dapr with &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/"&gt;.NET Aspire&lt;/a&gt; for an unparalleled local development experience&lt;/strong&gt;. We will create a few ASP.NET Core and Node.js services that will leverage &lt;strong&gt;service invocation&lt;/strong&gt;, &lt;strong&gt;state management&lt;/strong&gt;, and &lt;strong&gt;pub/sub&lt;/strong&gt;. The benefits are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A representation of your distributed system through compile-time constant, testable code.&lt;/li&gt;
&lt;li&gt;A centralized OpenTelemetry web dashboard to browse your traces, logs and metrics.&lt;/li&gt;
&lt;li&gt;A simple way to attach Dapr sidecars to your applications.&lt;/li&gt;
&lt;li&gt;Little or no YAML configuration files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using .NET Aspire for Dapr will &lt;strong&gt;reduce the onboarding time&lt;/strong&gt; for your developers. They can focus on using Dapr for feature development and spend less time setting up their local environment. Thanks to the integration with OpenTelemetry, it will be easier to troubleshoot interactions between multiple applications locally, which is usually done in cloud environments after the code is deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example of a Dapr distributed system with .NET Aspire
&lt;/h2&gt;

&lt;p&gt;The goal of our Dapr experiment with .NET Aspire is to create &lt;strong&gt;three services and the .NET Aspire host project&lt;/strong&gt;, which acts as the orchestrator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Alice&lt;/strong&gt;, an ASP.NET Core service that uses Dapr's service invocation to retrieve weather data from another service and caches it using the state store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bob&lt;/strong&gt;, an ASP.NET Core service that returns fake weather data, then publishes a "weather forecast requested" event using pub/sub.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carol&lt;/strong&gt;, a Node.js Express web application that subscribes to "weather forecast requested" events.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;complete code ready for use&lt;/strong&gt; is available in this &lt;a href="https://github.com/asimmon/aspire-dapr-demo"&gt;GitHub repository&lt;/a&gt; ⭐. The README will guide you to install the prerequisites and start the services. There's too much code to show in this post, so I will only provide a few snippets and screenshots.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyllteqj1i2d3c2u9zp6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyllteqj1i2d3c2u9zp6v.png" alt="Interactions between our services with Dapr." width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code below is the .NET Aspire host project where we declare these services, Dapr components and their relationships, no YAML involved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;Aspire.Hosting.Dapr&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;Microsoft.Extensions.Hosting&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;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stateStore&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;AddDaprStateStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"statestore"&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;pubSub&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;AddDaprPubSub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pubsub"&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="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspireDaprDemo_AliceService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDaprSidecar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubSub&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="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspireDaprDemo_BobService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"bob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDaprSidecar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubSub&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;AddNpmApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"carol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&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="s"&gt;"AspireDaprDemo.CarolService"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"watch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&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="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NODE_TLS_REJECT_UNAUTHORIZED"&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="n"&gt;Environment&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="s"&gt;"0"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDaprSidecar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubSub&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="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When launched, the Aspire starts all the services and offers a &lt;strong&gt;complete view of the distributed system&lt;/strong&gt; in the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnsopjctdhu5mb3ooz6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnsopjctdhu5mb3ooz6e.png" alt="The Aspire dashboard showing all services including Dapr sidecars." width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the Alice service exposes an endpoint &lt;code&gt;/weatherforecast&lt;/code&gt; that triggers the interactions described above. Here's what the OpenTelemetry trace looks like when invoking this endpoint:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqawbhcjo8llvkllfr0sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqawbhcjo8llvkllfr0sc.png" alt="It's easy to understand how Dapr behaves when your apps are instrumented with OpenTelemetry." width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A developer joining the development team could quickly understand how the different components of the distributed system interact with each other. In this screenshot, we can see that the flaky Bob service returned an error and that Dapr automatically retried the operation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To achieve this result, it is important that your applications are correctly instrumented with the OpenTelemetry SDK. Otherwise, only Dapr spans would have appeared.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;.NET Aspire offers a better way to visualize OpenTelemetry traces than the default Zipkin instance provided with Dapr because not only are the traces visually clearer, but the dashboard also includes logs and metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpa74bk1s09mk1wdp66h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpa74bk1s09mk1wdp66h.png" alt="OpenTelemetry logs and traces are linked together." width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dapr with .NET Aspire is configuration-free and easy to use
&lt;/h2&gt;

&lt;p&gt;Normally, to configure Dapr, you need to create YAML configuration files that describe the applications, sidecars, and networking details such as TCP ports. With .NET Aspire, this is not required.&lt;/p&gt;

&lt;p&gt;The communication between Alice and Bob, whose names were declared in the Aspire host project, is as simple as this thanks to the Dapr SDK (&lt;a href="https://github.com/asimmon/aspire-dapr-demo/blob/main/AspireDaprDemo.AliceService/Program.cs#L26"&gt;link to the full code&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// - "bob" is the name of the service declared in the Aspire host project&lt;/span&gt;
&lt;span class="c1"&gt;// - "weatherforecast" is the HTTP endpoint to invoke (URL path)&lt;/span&gt;
&lt;span class="c1"&gt;// - "client" is an instance of DaprClient available through dependency injection&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;forecasts&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeMethodAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherForecast&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bob"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No URLs were configured in &lt;code&gt;appsettings.json&lt;/code&gt; or environment variables. Using the service name &lt;code&gt;bob&lt;/code&gt; is the only constant required. Dapr is responsible for routing the request to the correct service.&lt;/p&gt;

&lt;p&gt;The same is true for the state store and pub/sub. Connection details are only known to the Dapr sidecars, so the applications don't need to worry about them. This avoids the tedious management of configuration files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine having 10 services in your distributed system, along with 4 environments: local, dev, stg, and prod. This could represent 40 potential configuration files to maintain, with dozens of URLs and connection strings. Thanks to Dapr, you no longer have to worry about this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using the state store and pub/sub is just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retrieve the weather forecast from the state store "statestore" declared in the Aspire host&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cachedForecasts&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStateAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CachedWeatherForecast&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"statestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cache"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// [...]&lt;/span&gt;
&lt;span class="c1"&gt;// Save the weather forecast in the state store under the key "cache"&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveStateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"statestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cache"&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;CachedWeatherForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forecasts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Publish an event "WeatherForecastMessage" to the pub/sub "pubsub" declared in the Aspire host, with the topic "weather"&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishEventAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pubsub"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"weather"&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;WeatherForecastMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Weather forecast requested!"&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 snippet of the Carol service which subscribes to the "weather" topic. Remember that both .NET Aspire and Dapr are language-agnostic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Events are received through HTTP POST requests (push delivery model)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/subscriptions/weather&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;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Weather forecast message received:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;h2&gt;
  
  
  How does .NET Aspire work with Dapr?
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;WithDaprSidecar&lt;/code&gt; method on a resource instructs .NET Aspire to start an instance of the &lt;code&gt;dapr&lt;/code&gt; executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// [...]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDaprSidecar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubSub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arguments passed to &lt;code&gt;dapr&lt;/code&gt; depend on the number of components referenced by the service and the options that may be passed during the invocation of the methods above.&lt;/p&gt;

&lt;p&gt;Here is the &lt;code&gt;dapr&lt;/code&gt; command that is executed for the Alice service on my computer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\dapr\dapr.exe run --app-id alice --resources-path C:\src\aspire-dapr-demo\resources --resources-path C:\Users\simmo\AppData\Local\Temp\aspire-dapr.xkxkzt4n.xcz\statestore --resources-path C:\Users\simmo\AppData\Local\Temp\aspire-dapr.xkxkzt4n.xcz\pubsub --app-port 5555 --dapr-grpc-port 58341 --dapr-http-port 58342 --metrics-port 58343 --app-channel-address localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two key points to remember here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The YAML code for built-in components in .NET Aspire, such as state store and pub/sub, is automatically generated in a temporary folder.&lt;/li&gt;
&lt;li&gt;By default, random ports are assigned, so you don't have to remember them or worry about possible collisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to learn more, the magic happens in the &lt;a href="https://github.com/dotnet/aspire/blob/v8.0.0-preview.6.24214.1/src/Aspire.Hosting.Dapr/DaprDistributedApplicationLifecycleHook.cs"&gt;DaprDistributedApplicationLifecycleHook&lt;/a&gt; class in the .NET Aspire source code.&lt;/p&gt;

&lt;p&gt;Subsequently, the orchestrated applications are passed environment variables that allow the Dapr SDK to communicate with the sidecars. This can be seen in the details of the resources on the Aspire dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyuqupjin7cm7jyh66sd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyuqupjin7cm7jyh66sd.png" alt="Dapr GRPC and HTTP endpoints are passed through environment variables to your apps." width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling more complex Dapr scenarios
&lt;/h2&gt;

&lt;p&gt;In this experiment, we used two Dapr components supported natively by .NET Aspire. However, it's possible to declare other types of components with the &lt;code&gt;AddDaprComponent&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDaprComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localsecretstore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"secretstores.local.file"&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;DaprComponentOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;LocalPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/component-config.yaml"&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also possible to declare resources such as resilience policies and assign them to sidecars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspireDaprDemo_AliceService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDaprSidecar&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;DaprSidecarOptions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ResourcesPaths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/resources-directory"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubSub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/asimmon/aspire-dapr-demo"&gt;Full code sample for this blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/fr-fr/dotnet/aspire/"&gt;.NET Aspire documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.dapr.io/"&gt;Dapr documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspire-samples/tree/main/samples/AspireWithDapr"&gt;.NET Aspire code samples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dapr/quickstarts"&gt;Dapr quickstart code samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
      <category>dapr</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Configure Renovate to handle nuspec files</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Sat, 04 May 2024 20:34:27 +0000</pubDate>
      <link>https://forem.com/asimmon/configure-renovate-to-handle-nuspec-files-4c79</link>
      <guid>https://forem.com/asimmon/configure-renovate-to-handle-nuspec-files-4c79</guid>
      <description>&lt;p&gt;I &lt;a href="https://anthonysimmon.com/nuget-version-range-updates-dotnet-renovate/"&gt;recently mentioned that Renovate's NuGet manager only supports certain files by default&lt;/a&gt;, and &lt;code&gt;.nuspec&lt;/code&gt; files are not among them. These are &lt;a href="https://learn.microsoft.com/en-us/nuget/reference/nuspec"&gt;XML manifests that describe the metadata of a NuGet package&lt;/a&gt;. Although nowadays, &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#project-files"&gt;SDK-style projects&lt;/a&gt; are sufficient for most cases to describe and generate NuGet packages, there are still many very popular projects that rely on &lt;code&gt;.nuspec&lt;/code&gt; files, as shown by this &lt;a href="https://github.com/search?q=path%3A*.nuspec&amp;amp;type=code"&gt;search on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.nuspec&lt;/code&gt; files can contain references to dependencies, making them important to consider in the Renovate update process, primarily for security reasons. Once again, we will use Renovate's extensibility with regular expressions to enable it to handle these files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renovate configuration for handling nuspec files
&lt;/h2&gt;

&lt;p&gt;The following Renovate configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detects files with the &lt;code&gt;.nuspec&lt;/code&gt; extension,&lt;/li&gt;
&lt;li&gt;Uses a regex to parse the dependencies and their versions,&lt;/li&gt;
&lt;li&gt;Applies the update management that would be used for NuGet.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.renovatebot.com/renovate-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&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="s2"&gt;"config:best-practices"&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;"enabledManagers"&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="s2"&gt;"nuget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"custom.regex"&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;"customManagers"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nuspec files manager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"customType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fileMatch"&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="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.nuspec$"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchStringsStrategy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"any"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchStrings"&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="s2"&gt;"&amp;lt;dependency&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+id=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;(?&amp;lt;depName&amp;gt;.*?)&lt;/span&gt;&lt;span class="se"&gt;\"\\&lt;/span&gt;&lt;span class="s2"&gt;s+version=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;(?&amp;lt;currentValue&amp;gt;.*?)&lt;/span&gt;&lt;span class="se"&gt;\"\\&lt;/span&gt;&lt;span class="s2"&gt;s*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;/&amp;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;"datasourceTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nuget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"versioningTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nuget"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the configuration
&lt;/h2&gt;

&lt;p&gt;We can validate this configuration against a &lt;code&gt;.nuspec&lt;/code&gt; file containing a reference to an old version of the C# MongoDB driver which contains a security vulnerability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;metadata&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;MyLibrary&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;$version$&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Example nuspec file with an outdated, vulnerable dependency&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;authors&amp;gt;&lt;/span&gt;johndoe&lt;span class="nt"&gt;&amp;lt;/authors&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;dependency&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"MongoDB.Driver"&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"2.18.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/metadata&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/package&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;a href="https://anthonysimmon.com/locally-test-validate-renovate-config-files/"&gt;running Renovate locally&lt;/a&gt;, we can see that the &lt;code&gt;MongoDB.Driver&lt;/code&gt; dependency is detected and Renovate recommends updating it to version &lt;code&gt;2.25.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG: packageFiles with updates (repository=local)
       "config": {
         "regex": [
           {
             "deps": [
               {
                 "depName": "MongoDB.Driver",
                 "currentValue": "2.18.0",
                 "datasource": "nuget",
                 "versioning": "nuget",
                 "replaceString": "&amp;lt;dependency id=\"MongoDB.Driver\" version=\"2.18.0\" /&amp;gt;",
                 "updates": [
                   {
                     "bucket": "non-major",
                     "newVersion": "2.25.0",
                     "newValue": "2.25.0",
                     "releaseTimestamp": "2024-04-12T21:27:47.967Z",
                     "newMajor": 2,
                     "newMinor": 25,
                     "updateType": "minor",
                     "branchName": "renovate/mongo-csharp-driver-monorepo"
                   }
                 ],
                 "packageName": "MongoDB.Driver",
                 "warnings": [],
                 "sourceUrl": "https://github.com/mongodb/mongo-csharp-driver",
                 "registryUrl": "https://api.nuget.org/v3/index.json",
                 "homepage": "https://www.mongodb.com/docs/drivers/csharp/",
                 "currentVersion": "2.18.0",
                 "isSingleVersion": true,
                 "fixedVersion": "2.18.0"
               }
             ],
             "matchStrings": [
               "&amp;lt;dependency\\s+id=\"(?&amp;lt;depName&amp;gt;.*?)\"\\s+version=\"(?&amp;lt;currentValue&amp;gt;.*?)\"\\s*\\/&amp;gt;"
             ],
             "matchStringsStrategy": "any",
             "datasourceTemplate": "nuget",
             "versioningTemplate": "nuget",
             "packageFile": "MyLibrary.nuspec"
           }
         ]
       }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/nuget/reference/nuspec"&gt;nuspec File Reference for NuGet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.renovatebot.com/modules/manager/nuget/"&gt;Renovate's NuGet manager documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>renovate</category>
      <category>csharp</category>
      <category>security</category>
    </item>
    <item>
      <title>Configure Renovate to update preview versions of NuGet packages</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Sat, 04 May 2024 20:32:25 +0000</pubDate>
      <link>https://forem.com/asimmon/configure-renovate-to-update-preview-versions-of-nuget-packages-1lch</link>
      <guid>https://forem.com/asimmon/configure-renovate-to-update-preview-versions-of-nuget-packages-1lch</guid>
      <description>&lt;p&gt;By default, Renovate ignores preview versions of dependencies. For NuGet, a preview version is a package whose version contains a semantic suffix such as &lt;code&gt;-alpha&lt;/code&gt;, &lt;code&gt;-beta&lt;/code&gt;, &lt;code&gt;-rc&lt;/code&gt;. There are some well-known NuGet packages that are only available in preview versions. For example, &lt;a href="https://www.nuget.org/packages/Aspire.Hosting/"&gt;Aspire.Hosting&lt;/a&gt; will likely remain in preview until the release of .NET 9, &lt;a href="https://www.nuget.org/packages/StyleCop.Analyzers/"&gt;StyleCop.Analyzers&lt;/a&gt; has been in beta for already 5 years, while &lt;a href="https://www.nuget.org/packages/OpenTelemetry.Instrumentation.GrpcNetClient/"&gt;OpenTelemetry.Instrumentation.GrpcNetClient&lt;/a&gt; and &lt;a href="https://www.nuget.org/packages/Azure.AI.OpenAI/"&gt;Azure.AI.OpenAI&lt;/a&gt; have never had a stable version.&lt;/p&gt;

&lt;p&gt;Yet, these packages might be used today in production in your applications and services, and it is just as important to keep them updated as the other stable packages. To allow Renovate to update these packages, you must use the configuration property &lt;code&gt;ignoreUnstable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.renovatebot.com/renovate-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&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="s2"&gt;"config:best-practices"&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;"enabledManagers"&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="s2"&gt;"nuget"&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;"packageRules"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchManagers"&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="s2"&gt;"nuget"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchPackageNames"&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="s2"&gt;"Aspire.Hosting"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".NET Aspire is currently only available in preview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ignoreUnstable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;HERE&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we have a .NET Aspire host project for which we wish to keep the &lt;code&gt;Aspire.Hosting&lt;/code&gt; dependency up to date. Here is the content of the csproj file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;net8.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;IsAspireHost&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/IsAspireHost&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InvariantGlobalization&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/InvariantGlobalization&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;"Aspire.Hosting"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"8.0.0-preview.2.23619.3"&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;When &lt;a href="https://anthonysimmon.com/locally-test-validate-renovate-config-files/"&gt;running Renovate locally&lt;/a&gt;, it is capable of finding a newer version of &lt;code&gt;Aspire.Hosting&lt;/code&gt;, &lt;code&gt;8.0.0-preview.5.24201.12&lt;/code&gt;, which was released on the day I write these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG: packageFiles with updates (repository=local)
       "config": {
         "nuget": [
           {
             "deps": [
               {
                 "datasource": "nuget",
                 "depType": "nuget",
                 "depName": "Aspire.Hosting",
                 "currentValue": "8.0.0-preview.2.23619.3",
                 "updates": [
                   {
                     "bucket": "non-major",
                     "newVersion": "8.0.0-preview.5.24201.12",
                     "newValue": "8.0.0-preview.5.24201.12",
                     "releaseTimestamp": "2024-04-09T14:50:53.883Z",
                     "newMajor": 8,
                     "newMinor": 0,
                     "updateType": "patch",
                     "branchName": "renovate/aspire.hosting-8.x"
                   }
                 ],
                 "packageName": "Aspire.Hosting",
                 "versioning": "nuget",
                 "warnings": [],
                 "sourceUrl": "https://github.com/dotnet/aspire",
                 "registryUrl": "https://api.nuget.org/v3/index.json",
                 "currentVersion": "8.0.0-preview.2.23619.3",
                 "isSingleVersion": true,
                 "fixedVersion": "8.0.0-preview.2.23619.3"
               }
             ],
             "packageFile": "MyProject.csproj"
           }
         ]
       }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.renovatebot.com/configuration-options/#ignoreunstable"&gt;Renovate's &lt;code&gt;ignoreUnstable&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/nuget/create-packages/prerelease-packages"&gt;Pre-release versions in NuGet packages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>renovate</category>
      <category>security</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Referencing external Docker containers in .NET Aspire using the new custom resources API</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Sat, 04 May 2024 17:13:51 +0000</pubDate>
      <link>https://forem.com/asimmon/referencing-external-docker-containers-in-net-aspire-using-the-new-custom-resources-api-4ifg</link>
      <guid>https://forem.com/asimmon/referencing-external-docker-containers-in-net-aspire-using-the-new-custom-resources-api-4ifg</guid>
      <description>&lt;p&gt;Up until now, the application model of .NET Aspire &lt;a href="https://anthonysimmon.com/exploring-microsoft-developer-control-plane-core-dotnet-aspire-dotnet-8/"&gt;was limited to two types of resources&lt;/a&gt;, namely &lt;strong&gt;executables&lt;/strong&gt; and &lt;strong&gt;containers&lt;/strong&gt;. The underlying DCP (Developer Control Plane) orchestrator is responsible for managing the lifecycle of these resources, including their creation, start, stop, and destruction.&lt;/p&gt;

&lt;p&gt;David Fowler &lt;a href="https://twitter.com/davidfowl/status/1774234866785378445"&gt;recently tweeted about the extensibility of the application model&lt;/a&gt;: "&lt;em&gt;.NET Aspire is optimized for developers. With the application model, you can use .NET Code to build up an understanding of your application and its dependencies. This builds an object model that represents a resource graph.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;However, &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/preview-5"&gt;.NET Aspire preview 5&lt;/a&gt; changes the status quo by &lt;strong&gt;allowing the application model to reach a whole new level of extensibility&lt;/strong&gt;. Now, it is possible to &lt;strong&gt;create custom resources&lt;/strong&gt;, which can be fully controlled by your C# code and will appear in the dashboard. Among other things, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change their status (starting, running, finished),&lt;/li&gt;
&lt;li&gt;Declare URLs to access these resources,&lt;/li&gt;
&lt;li&gt;Add custom properties that can be used in lifecycle hooks,&lt;/li&gt;
&lt;li&gt;Emit logs that will appear in the dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are numerous potential applications for this new extensibility model, and I am eager to see what the community will do with it. For this article, we will add to the dashboard a &lt;strong&gt;container that is not controlled by .NET Aspire&lt;/strong&gt;. One can imagine a development team that has not yet migrated their &lt;code&gt;docker-compose.yml&lt;/code&gt; but would still like to see the containers and their logs appear in the .NET Aspire dashboard.&lt;/p&gt;

&lt;p&gt;Another motivation would be for a developer to want a Docker container to continue its execution outside of an Aspire context, which is impossible today because Aspire destroys containers at the end of its execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving and displaying external container logs
&lt;/h2&gt;

&lt;p&gt;We will create the following API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IDistributedApplicationBuilder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddExternalContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;resource-name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;external-container-name-or-id&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;We will use the command &lt;code&gt;docker logs --follow CONTAINER-NAME-OR-ID&lt;/code&gt; to retrieve the logs of the external container. To facilitate the execution of the external &lt;code&gt;docker&lt;/code&gt; process, we will use &lt;a href="https://github.com/Tyrrrz/CliWrap"&gt;CliWrap&lt;/a&gt; instead of the primitive &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=net-8.0"&gt;Process&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is what the resource will look like in the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z9rtvumuk1upi3ua9b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z9rtvumuk1upi3ua9b5.png" alt="A MongoDB container is shown in the dashboard but is actually orchestrated by Docker Compose, outside of Aspire" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the log output from the external container:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5x66a431tl55if2iwq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5x66a431tl55if2iwq8.png" alt="The MongoDB logs are forwarded from Docker to the dashboard" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the external container is a persistent MongoDB instance configured as a &lt;a href="https://anthonysimmon.com/the-only-local-mongodb-replica-set-with-docker-compose-guide-youll-ever-need/"&gt;single-node replica set as described in my article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the external container custom resource API
&lt;/h2&gt;

&lt;p&gt;In our .NET Aspire app host project, let's start by adding the definition of our custom resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExternalContainerResource&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="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;containerNameOrId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Resource&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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ContainerNameOrId&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="n"&gt;containerNameOrId&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;Next, let's add the &lt;code&gt;AddExternalContainer&lt;/code&gt; extension method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExternalContainerResourceExtensions&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;IResourceBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExternalContainerResource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AddExternalContainer&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;IDistributedApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&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="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;containerNameOrId&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="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryAddLifecycleHook&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExternalContainerResourceLifecycleHook&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;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddResource&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;ExternalContainerResource&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;containerNameOrId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithInitialState&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;CustomResourceSnapshot&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ResourceType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"External container"&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="s"&gt;"Starting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Properties&lt;/span&gt; &lt;span class="p"&gt;=&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;ResourcePropertySnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomResourceKnownProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Custom"&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;ExcludeFromManifest&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 method will have the effect of adding our resource to the dashboard with the status "Starting" and the source "Custom," which will allow us to distinguish Aspire-controlled resources from custom resources.&lt;/p&gt;

&lt;p&gt;All that remains is to define the &lt;code&gt;ExternalContainerResourceLifecycleHook&lt;/code&gt; lifecycle hook, which will be responsible for executing the command &lt;code&gt;docker logs --follow CONTAINER-NAME-OR-ID&lt;/code&gt;, changing the resource status, and forwarding the logs to the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExternalContainerResourceLifecycleHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResourceNotificationService&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ResourceLoggerService&lt;/span&gt; &lt;span class="n"&gt;loggerService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDistributedApplicationLifecycleHook&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;CancellationTokenSource&lt;/span&gt; &lt;span class="n"&gt;_tokenSource&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="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;BeforeStartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DistributedApplicationModel&lt;/span&gt; &lt;span class="n"&gt;appModel&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="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;resource&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;appModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExternalContainerResource&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartTrackingExternalContainerLogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&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;_tokenSource&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="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;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartTrackingExternalContainerLogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExternalContainerResource&lt;/span&gt; &lt;span class="n"&gt;resource&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="kt"&gt;var&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;loggerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&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;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithArguments&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--follow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerNameOrId&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;cmdEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAsync&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="k"&gt;await&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;cmdEvent&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmdEvents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;StartedCommandEvent&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;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishUpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&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="s"&gt;"Running"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ExitedCommandEvent&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;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishUpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&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="s"&gt;"Finished"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;StandardOutputCommandEvent&lt;/span&gt; &lt;span class="n"&gt;stdOut&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;"External container {ResourceName} stdout: {StdOut}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&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;stdOut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;StandardErrorCommandEvent&lt;/span&gt; &lt;span class="n"&gt;stdErr&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;"External container {ResourceName} stderr: {StdErr}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&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;stdErr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="k"&gt;break&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="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="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&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;_tokenSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&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;default&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This evolution of the .NET Aspire application model in this preview 5 opens new perspectives for developers. Combined with your own lifecycle hooks, flexibility and extensibility are greater than ever. The introduction of the new method &lt;code&gt;IDistributedApplicationLifecycleHook.AfterEndpointsAllocatedAsync&lt;/code&gt; following the resolution of the &lt;a href="https://github.com/dotnet/aspire/issues/2155"&gt;GitHub issue #2155&lt;/a&gt; I opened last February allows you to execute arbitrary code during a more advanced phase of the Aspire host lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/preview-5"&gt;What's new in .NET Aspire preview 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspire/blob/1b627b7d5d399d4f9118366e7611e11e56de4554/playground/CustomResources/CustomResources.AppHost/TestResource.cs#L30"&gt;Microsoft's custom resource playground sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/cli/docker/container/logs/"&gt;Docker logs command documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anthonysimmon.com/exploring-microsoft-developer-control-plane-core-dotnet-aspire-dotnet-8/"&gt;Exploring the Microsoft Developer Control Plane at the heart of the new .NET Aspire&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
      <category>docker</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Automated NuGet package version range updates in .NET projects using Renovate</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Fri, 12 Apr 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/automated-nuget-package-version-range-updates-in-net-projects-using-renovate-15il</link>
      <guid>https://forem.com/asimmon/automated-nuget-package-version-range-updates-in-net-projects-using-renovate-15il</guid>
      <description>&lt;p&gt;In &lt;a href="https://anthonysimmon.com/locally-test-validate-renovate-config-files/"&gt;my previous post about how to locally test and validate Renovate configuration files&lt;/a&gt;, we saw how Renovate can be helpful in keeping our dependencies up-to-date. It recommended updates for the &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt; package from &lt;code&gt;7.0.0&lt;/code&gt; to &lt;code&gt;7.0.1&lt;/code&gt; (minor) or &lt;code&gt;8.0.0&lt;/code&gt; (major).&lt;/p&gt;

&lt;p&gt;By default, the way Renovate handles NuGet package updates in .NET projects is suitable for the majority of cases. According to &lt;a href="https://docs.renovatebot.com/modules/manager/nuget/"&gt;the Renovate NuGet manager documentation&lt;/a&gt; (a manager is a module that manages updates for a type of dependency), the following files are analyzed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project files &lt;code&gt;.csproj&lt;/code&gt;, &lt;code&gt;.fsproj&lt;/code&gt;, and &lt;code&gt;.vbproj&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;MSBuild files &lt;code&gt;.props&lt;/code&gt; and &lt;code&gt;.targets&lt;/code&gt;, including &lt;code&gt;Directory.Build.props&lt;/code&gt;, &lt;code&gt;Directory.Build.targets&lt;/code&gt;, and &lt;code&gt;Directory.Packages.props&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;.NET tool installation manifests &lt;code&gt;dotnet-tools.json&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;.NET SDK version definition files &lt;code&gt;global.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any reference to a NuGet package in these files will be analyzed by Renovate, and if a new version is available, a pull request will potentially be created to update it.&lt;/p&gt;

&lt;p&gt;What is not specified in the Renovate NuGet manager documentation page is that it can only process packages referenced with the basic package version notation, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&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;"Microsoft.Extensions.Hosting"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"7.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, specifying a package version in a .NET project can be much more advanced than that. Indeed, it's possible to &lt;a href="https://learn.microsoft.com/en-us/nuget/concepts/package-versioning?tabs=semver20sort#version-ranges"&gt;specify a version range&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Notation&lt;/th&gt;
&lt;th&gt;Applied rule&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;x ≥ 1.0&lt;/td&gt;
&lt;td&gt;Minimum version, inclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1.0,)&lt;/td&gt;
&lt;td&gt;x ≥ 1.0&lt;/td&gt;
&lt;td&gt;Minimum version, inclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(1.0,)&lt;/td&gt;
&lt;td&gt;x &amp;gt; 1.0&lt;/td&gt;
&lt;td&gt;Minimum version, exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1.0]&lt;/td&gt;
&lt;td&gt;x == 1.0&lt;/td&gt;
&lt;td&gt;Exact version match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(,1.0]&lt;/td&gt;
&lt;td&gt;x ≤ 1.0&lt;/td&gt;
&lt;td&gt;Maximum version, inclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(,1.0)&lt;/td&gt;
&lt;td&gt;x &amp;lt; 1.0&lt;/td&gt;
&lt;td&gt;Maximum version, exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1.0,2.0]&lt;/td&gt;
&lt;td&gt;1.0 ≤ x ≤ 2.0&lt;/td&gt;
&lt;td&gt;Exact range, inclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(1.0,2.0)&lt;/td&gt;
&lt;td&gt;1.0 &amp;lt; x &amp;lt; 2.0&lt;/td&gt;
&lt;td&gt;Exact range, exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1.0,2.0)&lt;/td&gt;
&lt;td&gt;1.0 ≤ x &amp;lt; 2.0&lt;/td&gt;
&lt;td&gt;Mixed inclusive minimum and exclusive maximum version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Take, for example, the library &lt;a href="https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.Hangfire-1.6.0-beta.1/src/OpenTelemetry.Instrumentation.Hangfire/OpenTelemetry.Instrumentation.Hangfire.csproj#L10"&gt;OpenTelemetry.Instrumentation.Hangfire&lt;/a&gt;, which imports the &lt;code&gt;Hangfire.Core&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&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;"Hangfire.Core"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"[1.7.0,1.9.0)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, &lt;a href="https://docs.renovatebot.com/modules/versioning/#nuget-versioning"&gt;Renovate does not support NuGet version ranges by default&lt;/a&gt;. However, &lt;a href="https://docs.renovatebot.com/modules/manager/regex/"&gt;it is possible to extend its capabilities with regular expressions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's required to extract the dependency name and the current version. In my previous example, the dependency name is &lt;code&gt;Hangfire.Core&lt;/code&gt;, and I will consider the current version to be the left side of the range, which is &lt;code&gt;1.7.0&lt;/code&gt;. You are free to decide which part of the range you wish to update.&lt;/p&gt;

&lt;p&gt;Here's the regular expression that allows extracting the dependency name and the current version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PackageReference\s+Include="(?&amp;lt;depName&amp;gt;[^"]+)"\s+Version="\[(?&amp;lt;currentValue&amp;gt;[^,]+),.*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;depName&lt;/code&gt; and &lt;code&gt;currentValue&lt;/code&gt; are mandatory group names that can be interpreted by Renovate to perform the update.&lt;/p&gt;

&lt;p&gt;Let's update our Renovate file to include the custom regex manager with our regular expression. I've omitted any content from the file that's not relevant for this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.renovatebot.com/renovate-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&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="s2"&gt;"config:best-practices"&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;"enabledManagers"&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="s2"&gt;"nuget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"custom.regex"&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;"customManagers"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"customType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fileMatch"&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="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.csproj$"&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;"matchStrings"&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="s2"&gt;"&amp;lt;PackageReference&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+Include=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;(?&amp;lt;depName&amp;gt;[^&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]+)&lt;/span&gt;&lt;span class="se"&gt;\"\\&lt;/span&gt;&lt;span class="s2"&gt;s+Version=&lt;/span&gt;&lt;span class="se"&gt;\"\\&lt;/span&gt;&lt;span class="s2"&gt;[(?&amp;lt;currentValue&amp;gt;[^,]+),.*&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"datasourceTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nuget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"versioningTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nuget"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we execute Renovate locally with our new configuration, we should see that Renovate is capable of identifying the &lt;code&gt;1.7.0&lt;/code&gt; version of &lt;code&gt;Hangfire.Core&lt;/code&gt; and proposes an update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG: packageFiles with updates (repository=local)
       "config": {
         "regex": [
           {
             "deps": [
               {
                 "depName": "Hangfire.Core",
                 "currentValue": "1.7.0",
                 "datasource": "nuget",
                 "versioning": "nuget",
                 "replaceString": "&amp;lt;PackageReference Include=\"Hangfire.Core\" Version=\"[1.7.0,1.9.0)\"",
                 "updates": [
                   {
                     "bucket": "non-major",
                     "newVersion": "1.8.11",
                     "newValue": "1.8.11",
                     "releaseTimestamp": "2024-02-23T11:56:41.437Z",
                     "newMajor": 1,
                     "newMinor": 8,
                     "updateType": "minor",
                     "branchName": "renovate/hangfire-monorepo"
                   }
                 ],
                 "packageName": "Hangfire.Core",
                 "warnings": [],
                 "sourceUrl": "https://github.com/HangfireIO/Hangfire",
                 "registryUrl": "https://api.nuget.org/v3/index.json",
                 "homepage": "https://www.hangfire.io/",
                 "currentVersion": "1.7.0",
                 "isSingleVersion": true,
                 "fixedVersion": "1.7.0"
               }
             ],
             "matchStrings": [
               "&amp;lt;PackageReference\\s+Include=\"(?&amp;lt;depName&amp;gt;[^\"]+)\"\\s+Version=\"\\[(?&amp;lt;currentValue&amp;gt;[^,]+),.*\""
             ],
             "datasourceTemplate": "nuget",
             "versioningTemplate": "nuget",
             "packageFile": "Project.csproj"
           }
         ]
       }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Renovate Regex manager has many other parameters, so be sure to check out the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/nuget/concepts/package-versioning"&gt;NuGet package versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.renovatebot.com/modules/manager/regex/"&gt;Custom Renovate manager support using regexex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.renovatebot.com/modules/versioning/"&gt;Renovate versioning schemes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>renovate</category>
    </item>
    <item>
      <title>TreatWarningsAsErrors and warnaserror are not the same</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Wed, 10 Apr 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/treatwarningsaserrors-and-warnaserror-are-not-the-same-4h9c</link>
      <guid>https://forem.com/asimmon/treatwarningsaserrors-and-warnaserror-are-not-the-same-4h9c</guid>
      <description>&lt;p&gt;At work, my team recently developed a &lt;a href="https://github.com/gsoft-inc/wl-openapi-msbuild"&gt;custom MSBuild task&lt;/a&gt; designed to extract the OpenAPI specification from an ASP.NET Core application during compilation and validate it against &lt;a href="https://github.com/gsoft-inc/wl-api-guidelines"&gt;our API design guidelines&lt;/a&gt; with &lt;a href="https://docs.stoplight.io/docs/spectral/"&gt;Spectral&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When a Spectral rule is violated, the MSBuild task generates a warning. To ensure developers fix these issues, we planned for these warnings to be treated as errors in their CI pipeline. To achieve this, we relied on the &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; property, which is already widely used in our projects.&lt;/p&gt;

&lt;p&gt;Surprisingly, we discovered that the build of some projects did not fail despite the presence of warnings related to non-compliance with the OpenAPI specification. Here are the findings of my investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; only impacts the C# compiler
&lt;/h2&gt;

&lt;p&gt;Indeed, the &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings#treatwarningsaserrors"&gt;Microsoft documentation&lt;/a&gt; is clear on this matter:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; only impacts the C# compiler, not any other MSBuild tasks in your csproj file. The &lt;code&gt;warnaserror&lt;/code&gt; command line switch impacts all tasks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to ensure that there are absolutely no warnings in your build, you must include the &lt;code&gt;-warnaserror&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build &lt;span class="nt"&gt;-warnaserror&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;-warnaserror&lt;/code&gt; can produce output on errors, &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; does not
&lt;/h2&gt;

&lt;p&gt;The same documentation states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Secondly, the compiler doesn't produce any output on any warnings when &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; is used. The compiler produces output when the &lt;code&gt;warnaserror&lt;/code&gt; command line switch is used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you still wish to produce an assembly, despite there being warnings treated as errors, you can set &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt; to false and use &lt;code&gt;-warnaserror&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings"&gt;C# Compiler Options to report errors and warnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>msbuild</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Locally test and validate your Renovate configuration files</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Tue, 09 Apr 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/locally-test-and-validate-your-renovate-configuration-files-4n45</link>
      <guid>https://forem.com/asimmon/locally-test-and-validate-your-renovate-configuration-files-4n45</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/renovatebot/renovate"&gt;Renovate&lt;/a&gt; is an automated dependency management tool that can be used to keep your dependencies up-to-date. It can be configured to automatically create pull requests to update your dependencies, and it supports a wide range of package managers and platforms.&lt;/p&gt;

&lt;p&gt;To use Renovate, you need to create a &lt;a href="https://docs.renovatebot.com/configuration-options/"&gt;renovate.json configuration file&lt;/a&gt;. The creation process can take some time, as there are many configuration options available. Depending on the complexity of your project, the package managers you use, your platform (GitHub, GitLab, Azure DevOps, etc.), and your specific needs, you may need to go through several iterations to get a configuration that works well for you.&lt;/p&gt;

&lt;p&gt;In this article, we will see how to test and validate your Renovate configuration files locally, to avoid having to push changes to your source control and wait for your pipeline to see if Renovate works as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;We will be using the &lt;a href="https://docs.renovatebot.com/examples/self-hosting/"&gt;self-hosted version of Renovate&lt;/a&gt; distributed via npm, but you can adapt the scripts in this article to use the Docker version, or others. Make sure you have &lt;a href="https://nodejs.org/"&gt;Node.js&lt;/a&gt; installed on your machine.&lt;/p&gt;

&lt;p&gt;You also need a project with a Renovate configuration file, of course. For demonstration purposes, we will use a .NET project that contains a single dependency on an older version of &lt;a href="https://www.nuget.org/packages/Microsoft.Extensions.Hosting"&gt;Microsoft.Extensions.Hosting&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;TargetFramework&amp;gt;&lt;/span&gt;net8.0&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="c"&gt;&amp;lt;!-- The most recent version of Microsoft.Extensions.Hosting is 8.0.0 as of this writing. --&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;"Microsoft.Extensions.Hosting"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"7.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;Next, let's create a &lt;code&gt;renovate.json&lt;/code&gt; file at the root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.renovatebot.com/renovate-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&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="s2"&gt;"config:best-practices"&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;"enabledManagers"&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="s2"&gt;"nuget"&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="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;This simplistic Renovate configuration will serve as our example. Use your own Renovate configuration file if you already have one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launching Renovate locally
&lt;/h2&gt;

&lt;p&gt;It is possible to run Renovate locally to test your configuration. To do this, we will use the following command for Linux and macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debug npx renovate &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;--repository-cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Windows using PowerShell Core, use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pwsh -Command { $env:LOG_LEVEL="debug"; npx renovate --platform=local --repository-cache=reset }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three important points to remember here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LOG_LEVEL=debug&lt;/code&gt; is used to obtain additional debugging information. Thanks to this environment variable, Renovate will display the updates available for the packages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--platform=local&lt;/code&gt; is used to perform a &lt;a href="https://docs.renovatebot.com/modules/platform/local/"&gt;dry run&lt;/a&gt; on the local computer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--repository-cache=reset&lt;/code&gt; is used to force Renovate to ignore the cache. This can avoid incorrect results between your different modifications of your configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading the Renovate output logs
&lt;/h2&gt;

&lt;p&gt;In the .NET project of our example, we intentionally used an older version of &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt; (7.0.0) so that Renovate could find an update. Here is an excerpt from the Renovate logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG: packageFiles with updates (repository=local)
       "config": {
         "nuget": [
           {
             "deps": [
               {
                 "datasource": "nuget",
                 "depType": "nuget",
                 "depName": "Microsoft.Extensions.Hosting",
                 "currentValue": "7.0.0",
                 "updates": [
                   {
                     "bucket": "non-major",
                     "newVersion": "7.0.1",
                     "newValue": "7.0.1",
                     "releaseTimestamp": "2023-02-14T13:21:52.713Z",
                     "newMajor": 7,
                     "newMinor": 0,
                     "updateType": "patch",
                     "branchName": "renovate/dotnet-monorepo"
                   },
                   {
                     "bucket": "major",
                     "newVersion": "8.0.0",
                     "newValue": "8.0.0",
                     "releaseTimestamp": "2023-11-14T13:23:17.653Z",
                     "newMajor": 8,
                     "newMinor": 0,
                     "updateType": "major",
                     "branchName": "renovate/major-dotnet-monorepo"
                   }
                 ],
                 "packageName": "Microsoft.Extensions.Hosting",
                 "versioning": "nuget",
                 "warnings": [],
                 "sourceUrl": "https://github.com/dotnet/runtime",
                 "registryUrl": "https://api.nuget.org/v3/index.json",
                 "homepage": "https://dot.net/",
                 "currentVersion": "7.0.0",
                 "isSingleVersion": true,
                 "fixedVersion": "7.0.0"
               }
             ],
             "packageFile": "RenovateDemo.csproj"
           }
         ]
       }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how &lt;code&gt;config.nuget[0].deps[0].updates&lt;/code&gt; contains two updates for &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt;: one for a patch update (7.0.1) and one for a major update (8.0.0). This is the type of information you can expect to see when you run Renovate locally. The output might be more detailed or contain more information depending on your configuration and the packages you use.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>security</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Remove git hash from assembly informational version in .NET 8</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Mon, 08 Apr 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/remove-git-hash-from-assembly-informational-version-in-net-8-1fob</link>
      <guid>https://forem.com/asimmon/remove-git-hash-from-assembly-informational-version-in-net-8-1fob</guid>
      <description>&lt;p&gt;If you build your .NET projects with the .NET 8 SDK, you may have noticed that the &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/assembly/set-attributes#informational-attributes"&gt;assembly informational version&lt;/a&gt; now includes the &lt;strong&gt;git hash of the associated commit&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;informationalVersion&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;Program&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="n"&gt;GetCustomAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AssemblyInformationalVersionAttribute&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;InformationalVersion&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="n"&gt;informationalVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Prints something like&lt;/span&gt;
&lt;span class="c1"&gt;// 1.0.0+9aa8e4c69145f5b7b4b59c93f2a1ee5ea19b79f9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; in the .NET 8 SDK, which now includes &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/source-link"&gt;Source Link by default&lt;/a&gt;. Source Link allows packages and applications to embed information about the source control of generated artifacts. This affects any target framework, as long as the code is compiled with the .NET 8 SDK.&lt;/p&gt;

&lt;p&gt;If you had logic that uses the assembly informational version, it might not work at all anymore. To opt out of this feature, you need to add the following property in your .csproj file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;IncludeSourceRevisionInInformationalVersion&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/IncludeSourceRevisionInInformationalVersion&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your custom HttpClient delegating handlers should be transient</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Fri, 05 Apr 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/asimmon/your-custom-httpclient-delegating-handlers-should-be-transient-5gee</link>
      <guid>https://forem.com/asimmon/your-custom-httpclient-delegating-handlers-should-be-transient-5gee</guid>
      <description>&lt;p&gt;If you've ever encountered the following error, this article is for you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;InvalidOperationException: The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means you are using the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory"&gt;Microsoft.Extensions.Http&lt;/a&gt; library and have modified an &lt;code&gt;HttpClient&lt;/code&gt;'s handler pipeline by inserting a &lt;code&gt;DelegatingHandler&lt;/code&gt;. As the error indicates, you cannot reuse a handler across multiple &lt;code&gt;HttpClient&lt;/code&gt; instances. In other words, your &lt;code&gt;DelegatingHandler&lt;/code&gt; &lt;strong&gt;cannot be a singleton&lt;/strong&gt;. Here's an example of problematic code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyCustomDelegatingHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DelegatingHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Implementation omitted&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Registering the handler as a singleton is the mistake&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;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyCustomDelegatingHandler&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="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyClient"&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;MyCustomDelegatingHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is also susceptible to the error mentioned above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This same instance will be used for all named HttpClient "MyClient"&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;MyCustomDelegatingHandler&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;"MyClient"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpMessageHandler&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;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try to understand what's wrong here. The implementation of &lt;code&gt;IHttpClientFactory&lt;/code&gt;, the service producing &lt;code&gt;HttpClient&lt;/code&gt; instances, constructs the pipeline from multiple &lt;code&gt;DelegatingHandler&lt;/code&gt;s making up an &lt;code&gt;HttpClient&lt;/code&gt;. Each time an &lt;code&gt;HttpClient&lt;/code&gt; is requested, the pipeline &lt;em&gt;may&lt;/em&gt; be recreated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyfzeyzsiux0ksuow1jao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyfzeyzsiux0ksuow1jao.png" alt="HttpClient handler pipeline" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Different &lt;code&gt;DelegatingHandler&lt;/code&gt;s are linked to each other through the &lt;code&gt;InnerHandler&lt;/code&gt; property, thanks to the &lt;a href="https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Microsoft.Extensions.Http/src/HttpMessageHandlerBuilder.cs#L81"&gt;CreateHandlerPipeline&lt;/a&gt; method. This method checks that the &lt;code&gt;InnerHandler&lt;/code&gt; property isn't already set and throws the &lt;code&gt;InvalidOperationException&lt;/code&gt; mentioned above if it is.&lt;/p&gt;

&lt;p&gt;Handlers created as part of the &lt;code&gt;IHttpClientFactory&lt;/code&gt; implementation are temporarily stored in a cache, for a duration equal to &lt;a href="https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/Microsoft.Extensions.Http/src/HttpClientFactoryOptions.cs#L58"&gt;HttpClientFactoryOptions.HandlerLifetime&lt;/a&gt;. By default, &lt;strong&gt;this duration is 2 minutes&lt;/strong&gt;. During this time, as many &lt;code&gt;HttpClient&lt;/code&gt;s as needed can be created and used without issue.&lt;/p&gt;

&lt;p&gt;However, once the handler is removed from the cache, it will go through the &lt;code&gt;CreateHandlerPipeline&lt;/code&gt; method again, and if it is a singleton, the &lt;code&gt;InnerHandler&lt;/code&gt; property will already be set, leading to the exception being thrown.&lt;/p&gt;

&lt;p&gt;If you control the lifecycle of the &lt;code&gt;HttpClient&lt;/code&gt; and only create one throughout the lifespan of your application, you might never notice this issue. But often, developers overlook this aspect and are not aware of the exact context in which their &lt;code&gt;HttpClient&lt;/code&gt; is used.&lt;/p&gt;

&lt;p&gt;A particularly noticeable and error-prone example is that of &lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#how-to-use-typed-clients-with-ihttpclientfactory"&gt;typed clients&lt;/a&gt;. Typed clients are registered using the generic &lt;code&gt;AddHttpClient&amp;lt;TClient&amp;gt;&lt;/code&gt; method (and its overloads), but in my experience, few developers realize that the &lt;code&gt;TClient&lt;/code&gt; type becomes registered in services with a &lt;strong&gt;transient lifecycle&lt;/strong&gt;. Thus, requesting this &lt;code&gt;TClient&lt;/code&gt; multiple times will result in the creation of multiple &lt;code&gt;HttpClient&lt;/code&gt;s, causing the pipeline construction error if one of your delegating handlers is a singleton.&lt;/p&gt;

&lt;p&gt;The solution is simple: ensure that the handler factory passed to the &lt;code&gt;AddHttpMessageHandler&lt;/code&gt; method always returns a new instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Registering the handler as a transient is the fix&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;MyCustomDelegatingHandler&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="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyClient"&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;MyCustomDelegatingHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Or explicitly create a new instance each time&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;"MyClient"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpMessageHandler&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;MyCustomDelegatingHandler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to securely reverse-proxy ASP.NET Core web apps</title>
      <dc:creator>Anthony Simmon</dc:creator>
      <pubDate>Thu, 04 Apr 2024 21:12:18 +0000</pubDate>
      <link>https://forem.com/asimmon/how-to-securely-reverse-proxy-aspnet-core-web-apps-3h4c</link>
      <guid>https://forem.com/asimmon/how-to-securely-reverse-proxy-aspnet-core-web-apps-3h4c</guid>
      <description>&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel"&gt;Kestrel&lt;/a&gt; is the solid, fast, and reliable web server that powers ASP.NET Core applications. It is entirely capable of serving as a front-facing web server, as proven by the Azure teams when they chose Kestrel and YARP to handle &lt;a href="https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services"&gt;reverse-proxying all services hosted on Azure App Services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, it's very unlikely that .NET developers will directly expose their Kestrel-based web apps to the internet. Typically, we use other popular web servers like &lt;a href="https://nginx.org/"&gt;Nginx&lt;/a&gt;, &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt;, and &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt; to act as a reverse-proxy in front of Kestrel for various reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To add a &lt;a href="https://en.wikipedia.org/wiki/TLS_termination_proxy"&gt;TLS termination layer&lt;/a&gt;: the reverse-proxy manages the TLS encryption and decryption (HTTPS),&lt;/li&gt;
&lt;li&gt;To handle requests on a specific domain and port, often using a virtual host configuration,&lt;/li&gt;
&lt;li&gt;To implement security features such as rate limiting, request filtering, &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/HSTS"&gt;HSTS&lt;/a&gt;, etc.,&lt;/li&gt;
&lt;li&gt;To provide request compression and caching, depending on the end-user's browser capabilities,&lt;/li&gt;
&lt;li&gt;For load balancing, failover support, and response transformation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these features can be configured with Kestrel, managing them across many web apps is more straightforward with a unified configuration point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the need for header forwarding
&lt;/h2&gt;

&lt;p&gt;When placing a reverse-proxy in front of our ASP.NET Core applications, the apps may lose access to certain original HTTP request information, like the client's IP address, specific HTTP headers, the original domain, and port. We need this information for the proper functioning of our applications.&lt;/p&gt;

&lt;p&gt;This is where the concept of header forwarding comes into play. The reverse-proxy forwards the original HTTP request's information to the backend application server via specific headers. The most common forwarding headers are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;X-Forwarded-For&lt;/code&gt;: contains the original client's IP address,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Forwarded-Host&lt;/code&gt;: contains the original host requested by the client,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Forwarded-Proto&lt;/code&gt;: contains the original protocol (HTTP or HTTPS),&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Forwarded-Prefix&lt;/code&gt;: contains the original path base used by the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most ASP.NET Core applications, especially those that are containerized, listen on the loopback address and port &lt;code&gt;8080&lt;/code&gt;. Thus, &lt;code&gt;X-Forwarded-Host&lt;/code&gt; and &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; are sometimes essential in scenarios where ASP.NET Core needs to generate URLs, such as in Razor templates or when generating redirect links in an OAuth2 authorization flow.&lt;/p&gt;

&lt;p&gt;From a security perspective, &lt;code&gt;X-Forwarded-For&lt;/code&gt; is also crucial. Without this header, your application would not be able to obtain the client's IP address, potentially affecting logging, your authentication and authorization stack, rate limiting logic, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to securely interpret X-Forwarded-* headers in ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;Ensuring ASP.NET Core securely interprets &lt;code&gt;X-Forwarded-*&lt;/code&gt; headers is vital. Attackers could manipulate these headers to bypass security measures. How can we trust these header values?&lt;/p&gt;

&lt;p&gt;Your reverse-proxy is responsible for stripping these headers from client requests. If not done, an end-user could, for instance, impersonate an IP address. By default, well-configured reverse-proxies don't accept them and populate these headers with reliable values like the TCP connection and original HTTP headers such as &lt;code&gt;Host&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Avoid writing your logic to read and interpret &lt;code&gt;X-Forwarded-*&lt;/code&gt; headers. ASP.NET Core already includes middleware that handles this securely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&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;UseForwardedHeaders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Add this line&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="c1"&gt;// [...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UseForwardedHeaders&lt;/code&gt; should be placed before any other middleware, especially before &lt;code&gt;UseHsts&lt;/code&gt;, and can follow diagnostics and error handling middlewares.&lt;/p&gt;

&lt;p&gt;Remember to configure the middleware options to specify which headers you wish to forward; otherwise, the middleware will not take any action. For example, to forward &lt;code&gt;X-Forwarded-For&lt;/code&gt;, &lt;code&gt;X-Forwarded-Host&lt;/code&gt;, and &lt;code&gt;X-Forwarded-Proto&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ForwardedHeadersOptions&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="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;ForwardedHeaders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;ForwardedHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XForwardedFor&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ForwardedHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XForwardedProto&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ForwardedHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XForwardedHost&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://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions?view=aspnetcore-8.0"&gt;ForwardedHeadersOptions&lt;/a&gt; are very flexible, allowing you also to change the forwarding headers' names if you're not using the standard &lt;code&gt;X-Forwarded-*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httpoverrides.forwardedheadersmiddleware"&gt;ForwardedHeadersMiddleware&lt;/a&gt;, inserted via &lt;code&gt;UseForwardedHeaders&lt;/code&gt;, automatically updates HTTP request properties like &lt;code&gt;RemoteIpAddress&lt;/code&gt;, &lt;code&gt;Host&lt;/code&gt;, &lt;code&gt;Scheme&lt;/code&gt;, and &lt;code&gt;PathBase&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restricting IP addresses of reverse-proxies authorized to forward headers
&lt;/h2&gt;

&lt;p&gt;If you know the IP address or range of your reverse-proxy, you can specify them in the &lt;code&gt;ForwardedHeadersOptions&lt;/code&gt; to ensure that the &lt;code&gt;X-Forwarded-*&lt;/code&gt; headers come from a trusted source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ForwardedHeadersOptions&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 10.0.0.100 is the IP address of the reverse proxy&lt;/span&gt;
    &lt;span class="c1"&gt;// Use KnownProxies to add individual IP addresses you trust&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;KnownProxies&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;IPAddress&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="s"&gt;"10.0.0.100"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// In case you have a range of IP addresses you trust, you can use KnownNetworks&lt;/span&gt;
    &lt;span class="n"&gt;IPNetwork&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="s"&gt;"10.0.0.0/24"&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;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer"&gt;Configure ASP.NET Core to work with proxy servers and load balancers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For"&gt;X-Forwarded-For&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host"&gt;X-Forwarded-Host&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto"&gt;X-Forwarded-Proto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
