<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aspnet.net Core on ESG's</title><link>https://esg.dev/tags/aspnet.net-core/</link><description>Recent content in Aspnet.net Core on ESG's</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 17 Jul 2022 18:01:55 -0400</lastBuildDate><atom:link href="https://esg.dev/tags/aspnet.net-core/index.xml" rel="self" type="application/rss+xml"/><item><title>Deferred processing in ASP.NET Core 6</title><link>https://esg.dev/posts/deferred-processing-in-aspnetcore/</link><pubDate>Sun, 17 Jul 2022 18:01:55 -0400</pubDate><guid>https://esg.dev/posts/deferred-processing-in-aspnetcore/</guid><description>&lt;p&gt;Nowadays, a common pattern we see when treating HTTP requests is to do a minimal amount of work before returning a response, while treating longer operations (ex: sending email) in the background after the request has been completed.&lt;/p&gt;
&lt;p&gt;Unfortunately, in ASP.NET, we often see this being done in questionable ways, mainly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not using &lt;code&gt;await&lt;/code&gt; on &lt;code&gt;Task&lt;/code&gt; object&lt;/li&gt;
&lt;li&gt;Wrapping some code in &lt;code&gt;Task.Run&lt;/code&gt; and not awaiting the results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can lead to dependency injection issues (if your request ends, all of our scoped objected are gone), or having exceptions thrown into the wind.&lt;/p&gt;
&lt;p&gt;Fortunately, .NET has it&amp;rsquo;s own internal publisher/subscriber-like feature, called Channels.&lt;/p&gt;
&lt;h1 id="channels"&gt;Channels&lt;/h1&gt;
&lt;p&gt;Channels were first introducted in .NET Core 3.0. While they&amp;rsquo;ve had a few improvements since, the abstract classes were mostly unchanged.&lt;/p&gt;
&lt;h2 id="the-basics"&gt;The basics&lt;/h2&gt;
&lt;p&gt;A lot has been written about how to usage channels, so I won&amp;rsquo;t go in too deep here.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s go over the building blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Channel&lt;/code&gt;: Static class used to create channels&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Channel&amp;lt;T&amp;gt;&lt;/code&gt;: Abstract class representing a channel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChannelReader&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;ChannelWriter&amp;lt;T&amp;gt;&lt;/code&gt;: Abstract classes representing readers and writers to a channel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple setup would be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; channel = Channel.CreateUnbounded&amp;lt;Model&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; reader = channel.Reader;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; writer = channel.Writer;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="integration-with-aspnet-core"&gt;Integration with ASP.NET Core&lt;/h1&gt;
&lt;p&gt;We&amp;rsquo;ll be assuming that we want to process a model named, appropriately, &lt;code&gt;Model&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="background-service"&gt;Background service&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s created a &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&amp;amp;tabs=visual-studio"&gt;HostedService&lt;/a&gt; to receive the messages.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll be inheriting from &lt;code&gt;BackgroundService&lt;/code&gt; since it makes the implementation a little simpler.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ModelService&lt;/span&gt; : BackgroundService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ChannelReader&amp;lt;Model&amp;gt; _channelReader;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;Service&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ModelService(ChannelReader&amp;lt;Model&amp;gt; channelReader, ILogger&amp;lt;Service&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _channelReader = channelReader;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExecuteAsync(CancellationToken stoppingToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _channelReader.WaitToReadAsync(stoppingToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (_channelReader.TryRead(&lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; item))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;&amp;#34;Message is `{Message}`&amp;#34;&lt;/span&gt;, item.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pretty simple, right?&lt;/p&gt;
&lt;p&gt;We pass a &lt;code&gt;ChannelReader&amp;lt;Model&amp;gt;&lt;/code&gt; to the constructor&lt;/p&gt;
&lt;p&gt;In the first &lt;code&gt;while&lt;/code&gt;, we wait for a message to be available in the channel. When there&amp;rsquo;s one, we enter the second while to read every messages available until exiting.&lt;/p&gt;
&lt;p&gt;We could simplify this into a single while, but this version is a little more performant since it always to get multiple messages from a channel without having to &lt;code&gt;await&lt;/code&gt;. More information on &lt;a href="https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/"&gt;An Introduction to System.Threading.Channels&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="scoped-services"&gt;Scoped services&lt;/h3&gt;
&lt;p&gt;Note that the &lt;code&gt;BackgroundService&lt;/code&gt; here is a singleton, meaning that it won&amp;rsquo;t be able to access scoped services, such as EntityFramework Core&amp;rsquo;s &lt;code&gt;DbContext&lt;/code&gt;, which is often registered as scope.&lt;/p&gt;
&lt;p&gt;To get around that, you should inject an &lt;code&gt;IServiceProvider&lt;/code&gt; into your background service. Depending on performance consideration, I&amp;rsquo;d typically start with creating a new scope per incoming message, and looking into batching if there&amp;rsquo;s an issue.&lt;/p&gt;
&lt;h2 id="wiring-the-dependency-injection"&gt;Wiring the dependency injection&lt;/h2&gt;
&lt;p&gt;We start by wiring the channel itself, as long as its reader and writer.&lt;/p&gt;
&lt;p&gt;Note that I&amp;rsquo;m using minimal API syntax here, but it should be easy to adapt to the older style syntax like ASP.NET Core 5.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton(Channel.CreateUnbounded&amp;lt;Model&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton(p =&amp;gt; p.GetRequiredService&amp;lt;Channel&amp;lt;Model&amp;gt;&amp;gt;().Reader);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton(p =&amp;gt; p.GetRequiredService&amp;lt;Channel&amp;lt;Model&amp;gt;&amp;gt;().Writer);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;CreateUnbounded&lt;/code&gt; means that there won&amp;rsquo;t be any item limits on the channel, other than the amount of memory available to your application.&lt;/p&gt;
&lt;p&gt;then, we wire the service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHostedService&amp;lt;ModelService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="setting-up-the-route"&gt;Setting up the route&lt;/h2&gt;
&lt;p&gt;We then setup a route to receive the messages. To make things easier, I made one that receives an array.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapPost(&lt;span style="color:#e6db74"&gt;&amp;#34;/messages&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (ChannelWriter&amp;lt;Model&amp;gt; writer, ICollection&amp;lt;Model&amp;gt; models, CancellationToken cancellationToken) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; model &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; models)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; writer.WriteAsync(model, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; StatusCodes.Status202Accepted;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="testing-it"&gt;Testing it&lt;/h2&gt;
&lt;p&gt;You can try it by making a POST to &lt;code&gt;/Messages&lt;/code&gt; with a payload such as&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#f92672"&gt;&amp;#34;Message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hello world&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#f92672"&gt;&amp;#34;Message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hello too&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#f92672"&gt;&amp;#34;Message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hello three&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to make sure that the request returns before the messages are processed, you can add a &lt;code&gt;Task.Delay&lt;/code&gt; in &lt;code&gt;ModelService&lt;/code&gt; after each log message. The messages will slowly scroll in your application&amp;rsquo;s console, long after the request has completed.&lt;/p&gt;
&lt;h1 id="parting-words"&gt;Parting words&lt;/h1&gt;
&lt;p&gt;This was a very simple demo of channels with ASP.NET Core. You can find a working demo at &lt;a href="https://github.com/EricStG/AspNetCoreChannels"&gt;https://github.com/EricStG/AspNetCoreChannels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re considering using channels in your application, consider spending some time configuring your channels. For example, I&amp;rsquo;d look at whether I should use a bounded or unbounded channel, and if I&amp;rsquo;ll be using a single reader or writer. Having it properly configured will improve performance and avoid unplanned nastiness.&lt;/p&gt;</description></item></channel></rss>