Jekyll2023-12-12T04:03:41+00:00https://btburnett.com/feedRandomDevRandom development on many topics (but especially .NET and Couchbase)Brant BurnettGeneric Type Construction With Static Virtual Interface Members2022-12-12T05:00:00+00:002022-12-12T05:00:00+00:00https://btburnett.com/csharp/2023/12/12/new-generics<p class="notice--info">This blog is one of The December 12th entries on the <a href="https://www.csadvent.christmas/">2023 C# Advent Calendar</a>. Thanks for having me again!</p> <p>Typically for C# Advent I write about a new C# feature. And there are a lot of great new features this year in <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12">C# 12</a>. My favorite is probably <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#primary-constructors">primary constructors</a> because of the massive amount of boilerplate it replaces when writing classes that accept injected dependencies. I’m also super excited by the hidden performance optimizations found in <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#collection-expressions">collection expressions</a>.</p> <p>But, oddly, this year I’m going to talk about a new feature from last year. <a href="https://learn.microsoft.com/en-us/dotnet/standard/generics/math">Generic math</a> support was added in .NET 7 and is a great new feature by itself, but what’s even more interesting to me is how it was implemented. Under the hood, generic math support uses a new feature in C# 11 called <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members">static virtual interface members</a>.</p> <p>Static virtual interface members isn’t a purely C# 11 feature. Utilizing them requires .NET 7 or later because changes were also required in the .NET runtime. Since .NET 7 wasn’t a long-term support release I don’t think it’s gotten as much utilization as it deserves. But now .NET 8 is available and it is an LTS release, so I think it’s worth revisiting.</p> <p>Today, I’d like to demonstrate a different use case for static virtual interface members: generic type construction.</p> <h2 id="the-backstory">The Backstory</h2> <p>Since .NET 2.0 we’ve had support for generics in C#. Generics are a great way to write code that can be reused across a variety of types. For example, the <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code> class is a generic type which can be used to store a list of any distinct type while maintaining strong type controls and avoiding boxing of value types (unlike its predecessor <code class="language-plaintext highlighter-rouge">ArrayList</code>).</p> <p>Along with generics came the concept of <a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters">generic type constraints</a>. These allow you to specify that a generic type must be a particular type or implement a particular interface. Since the compiler then knows that the type will have certain members, it can allow you to use those members from within the generic code.</p> <p>One such constraint is the <code class="language-plaintext highlighter-rouge">new()</code> constraint. This requires that the type have a public parameterless constructor. With this constraint, you can use the <code class="language-plaintext highlighter-rouge">new</code> keyword to construct an instance of the type.</p> <p>Apologies in advance for the contrived example, but I promise scenarios like this do come up in real development.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Vehicle</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Car</span> <span class="p">:</span> <span class="n">Vehicle</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Motorcycle</span> <span class="p">:</span> <span class="n">Vehicle</span> <span class="p">{</span> <span class="p">}</span> <span class="c1">// This method may be called with either Car or Motorcycle to create</span> <span class="c1">// a concrete instance of an Vehicle.</span> <span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Create</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">models</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">Vehicle</span><span class="p">,</span> <span class="k">new</span><span class="p">()</span> <span class="p">{</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">model</span> <span class="k">in</span> <span class="n">models</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Constructs T, either a Car or Motorcycle, depending on the</span> <span class="c1">// generic type parameter used when calling Create&lt;T&gt;</span> <span class="k">yield</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">T</span><span class="p">()</span> <span class="p">{</span> <span class="n">Model</span> <span class="p">=</span> <span class="n">model</span> <span class="p">};</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="the-problem">The Problem</h2> <p>This works great for simple cases, but what if we wanted to accept parameters on the constructor? Perhaps we want to make the <code class="language-plaintext highlighter-rouge">Model</code> property read only and require it to be passed during construction.</p> <p class="notice--info"><strong>Note:</strong> I’m using C# 12 <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#primary-constructors">primary constructors</a> here!</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Vehicle</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">model</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Car</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">:</span> <span class="nf">Vehicle</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Motorcycle</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">:</span> <span class="nf">Vehicle</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="c1">// This method may be called with either Car or Motorcycle to create</span> <span class="c1">// a concrete instance of an Vehicle.</span> <span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Create</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">models</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">Vehicle</span><span class="p">,</span> <span class="k">new</span><span class="p">(</span><span class="kt">string</span><span class="p">)</span> <span class="c1">// THIS IS NOT ALLOWED, COMPILER ERROR</span> <span class="p">{</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">model</span> <span class="k">in</span> <span class="n">models</span><span class="p">)</span> <span class="p">{</span> <span class="k">yield</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">T</span><span class="p">(</span><span class="n">model</span><span class="p">);</span> <span class="c1">// THEREFORE, THIS IS NOT ALLOWED EITHER</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="enter-static-virtual-interface-members">Enter Static Virtual Interface Members</h2> <p>This is where static virtual interface members come in. With static virtual interface members, we can define a static method on an interface that can be called from within the generic code. And, in many ways, a constructor is really a special kind of static method. The differences between a constructor versus a static factory method that returns a new instance of <code class="language-plaintext highlighter-rouge">T</code> are minor. So, let’s see how we can use static virtual interface members to solve our problem.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// This interface defines a static abstract method, meaning that any class which implements</span> <span class="c1">// the interface must include a static method with the same signature. Note that generic TSelf</span> <span class="c1">// allows the Create method to return a strongly-typed instance.</span> <span class="k">public</span> <span class="k">interface</span> <span class="nc">IVehicleFactory</span><span class="p">&lt;</span><span class="n">TSelf</span><span class="p">&gt;</span> <span class="c1">// Constrain the generic type parameter to types that self-reference using IVehicleFactory&lt;TSelf&gt;.</span> <span class="c1">// The Vehicle constraint is optional in this example, but I like to include it for clarity.</span> <span class="k">where</span> <span class="n">TSelf</span> <span class="p">:</span> <span class="n">Vehicle</span><span class="p">,</span> <span class="n">IVehicleFactory</span><span class="p">&lt;</span><span class="n">TSelf</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">static</span> <span class="k">abstract</span> <span class="n">TSelf</span> <span class="nf">Create</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Vehicle</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Model</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">model</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// IVehicleFactory&lt;Car&gt; is included with a self-referencing generic type parameter</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Car</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">:</span> <span class="nf">Vehicle</span><span class="p">(</span><span class="n">model</span><span class="p">),</span> <span class="n">IVehicleFactory</span><span class="p">&lt;</span><span class="n">Car</span><span class="p">&gt;</span> <span class="p">{</span> <span class="c1">// The create method is implemented as a static method on the class,</span> <span class="c1">// using Car explicitly as the return type.</span> <span class="k">public</span> <span class="k">static</span> <span class="n">Car</span> <span class="nf">Create</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Car</span><span class="p">(</span><span class="n">model</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// Repeat for Motorcycle</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Motorcycle</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">:</span> <span class="nf">Vehicle</span><span class="p">(</span><span class="n">model</span><span class="p">),</span> <span class="n">IVehicleFactory</span><span class="p">&lt;</span><span class="n">Motorcycle</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="n">Motorcycle</span> <span class="nf">Create</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Motorcycle</span><span class="p">(</span><span class="n">model</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// This method may be called with either Car or Motorcycle to create</span> <span class="c1">// a concrete instance of an Vehicle.</span> <span class="k">public</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Create</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">models</span><span class="p">)</span> <span class="c1">// Requires that T implement IVehicleFactory&lt;T&gt; to gain access to the static method</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">Vehicle</span><span class="p">,</span> <span class="n">IVehicleFactory</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">model</span> <span class="k">in</span> <span class="n">models</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Reference the static method via T</span> <span class="k">yield</span> <span class="k">return</span> <span class="n">T</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">model</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>The main limitation of this approach is that you must be in control of the types of <code class="language-plaintext highlighter-rouge">TSelf</code>. If <code class="language-plaintext highlighter-rouge">Car</code> and <code class="language-plaintext highlighter-rouge">Motorcycle</code> were implemented in a third-party library then you may not be able to mark them with the <code class="language-plaintext highlighter-rouge">IVehicleFactory&lt;TSelf&gt;</code> interface. Even if they implement the required static factory method, if it isn’t marked with the interface then it won’t work. This is unlike the <code class="language-plaintext highlighter-rouge">new()</code> constraint which can be used with any type that has a public parameterless constructor.</p> <p>The other limitation is static virtual interface members can only be used when targeting .NET 7 or later. This makes them unavailable for legacy applications. It also limits their effectiveness for libraries shared via NuGet that include older TFMs like <code class="language-plaintext highlighter-rouge">netstandard2.0</code>.</p> <p>However, when they’re available to use, static virtual interface members can be a powerful tool for generic type construction that I feel is underutilized. I hope this post has helped you understand how to apply them to your code.</p>Brant BurnettThis blog is one of The December 12th entries on the 2023 C# Advent Calendar. Thanks for having me again!Using Roslyn to Power C# SDK Generation from OpenAPI Specifications2022-12-09T05:00:00+00:002022-12-09T05:00:00+00:00https://btburnett.com/csharp/2022/12/09/yardarm<p class="notice--info">This blog is one of The December 9th entries on the <a href="https://www.csadvent.christmas/">2022 C# Advent Calendar</a>. Believe it or not, this is my 6th year participating. Thanks for having me again Matt and Calvin!</p> <p>The <a href="https://www.openapis.org/">OpenAPI</a> set of standards (formerly known as Swagger) for defining HTTP-based APIs is a great set of tools. At <a href="https://centeredgesoftware.com/">CenterEdge Software</a> we normally use OpenAPI 3 specifications to describe many of our services, both internal and external, making it easy for applications to reach those services. We also typically use SDK generators to create C# SDKs directly from the API specifications.</p> <p>One day I asked myself “Hey, I wonder if I can use Roslyn to make an even better, faster OpenAPI SDK generator?” A couple years later and <a href="https://github.com/CenterEdge/Yardarm">Yardarm</a> is a real thing and the go-to choice at CenterEdge for C# SDK generation.</p> <h2 id="what-is-roslyn">What is Roslyn</h2> <p>For those that don’t know, <a href="https://github.com/dotnet/roslyn">Roslyn</a> is the modern C# compiler which is itself written in C# (dogfooding at its finest). It powers everything from compilation to C# source generators to the syntax hints in Visual Studio.</p> <p>There is a lot to Roslyn, it offers an incredible depth of functionality. But when it comes to its primary functionality, compiling a project, you can think of it as having three main components:</p> <ol> <li>A set of types to represent source code as an immutable syntax tree</li> <li>A parser that can read source code and turn it into a syntax tree</li> <li>A compiler that takes a syntax tree and produces output DLL files (and other related files like PDB debug files)</li> </ol> <p>For our purposes, we are primarily interested in creating a syntax tree. Here is some example code which builds a part of a syntax tree to define a method in a class:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">MethodDeclaration</span><span class="p">(</span> <span class="n">attributeLists</span><span class="p">:</span> <span class="k">default</span><span class="p">,</span> <span class="c1">// No attributes</span> <span class="n">modifiers</span><span class="p">:</span> <span class="n">SyntaxTokenList</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="nf">Token</span><span class="p">(</span><span class="n">SyntaxKind</span><span class="p">.</span><span class="n">PublicKeyword</span><span class="p">)),</span> <span class="c1">// Public keyword</span> <span class="n">returnType</span><span class="p">:</span> <span class="nf">PredefinedType</span><span class="p">(</span><span class="nf">Token</span><span class="p">(</span><span class="n">SyntaxKind</span><span class="p">.</span><span class="n">StringKeyword</span><span class="p">)),</span> <span class="c1">// Returns a string</span> <span class="n">explicitInterfaceSpecifier</span><span class="p">:</span> <span class="k">null</span><span class="p">,</span> <span class="n">identifier</span><span class="p">:</span> <span class="nf">Identifier</span><span class="p">(</span><span class="s">"BuildUri"</span><span class="p">),</span> <span class="c1">// Name of the method</span> <span class="n">typeParameterList</span><span class="p">:</span> <span class="k">default</span><span class="p">,</span> <span class="c1">// No type parameters (this isn't a generic method)</span> <span class="n">parameterList</span><span class="p">:</span> <span class="nf">ParameterList</span><span class="p">(),</span> <span class="c1">// No parameters so provide an empty list</span> <span class="n">constraintClauses</span><span class="p">:</span> <span class="k">default</span><span class="p">,</span> <span class="c1">// No generic type constraints</span> <span class="n">body</span><span class="p">:</span> <span class="nf">Block</span><span class="p">(</span> <span class="c1">// List of statements within the block body of the method</span> <span class="nf">ReturnStatement</span><span class="p">(</span><span class="nf">LiteralExpression</span><span class="p">(</span><span class="n">SyntaxKind</span><span class="p">.</span><span class="n">StringLiteralExpression</span><span class="p">,</span> <span class="nf">Literal</span><span class="p">(</span><span class="s">"uri"</span><span class="p">)))</span> <span class="p">),</span> <span class="n">expressionBody</span><span class="p">:</span> <span class="k">null</span><span class="p">,</span> <span class="c1">// If we're using =&gt; expression syntax instead of a { ... } block, this would go here</span> <span class="n">semicolonToken</span><span class="p">:</span> <span class="k">default</span> <span class="c1">// No trailing semicolon since we're not using expression syntax</span> <span class="p">);</span> </code></pre></div></div> <p>This produces the following C# code:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">BuildUri</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"uri"</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>This seems very verbose, but there are also plenty of overloads that help with shorter code. I included a more complete example for clarity.</p> <p>A single syntax tree, from the root node down, covers every detail behind a single C# code file from using statements to class declarations to the statements in a method body. A collection of syntax trees, combined with other information like referenced assemblies and compiler options, make up a <code class="language-plaintext highlighter-rouge">CSharpCompilation</code>.</p> <p class="notice--info">A note for all the VB and F# folks: Roslyn isn’t only for C#, it’s designed to be language agnostic and also powers other languages in the .NET ecosystem.</p> <p>Oh, and here’s a <a href="https://roslynquoter.azurewebsites.net/">great online tool for turning C# into Roslyn creation code</a>.</p> <h2 id="performance">Performance</h2> <p>The Roslyn C# compiler is fast, Microsoft and others have spent an inordinate amount of time optimizing it. The above approach to generating code works to our advantage when we use it during SDK generation.</p> <p>A typical SDK generator works using this flow:</p> <ol> <li>Apply templates to the OpenAPI specification to generate C# files</li> <li>Write the C# files and a supporting <code class="language-plaintext highlighter-rouge">csproj</code> file to disk</li> <li>Run the Roslyn compiler as a separate process</li> <li>Roslyn reads all the files from disk</li> <li>Roslyn runs the files through a parser to generate a syntax tree</li> <li>Roslyn generates the compiled output from the syntax tree and writes to disk</li> </ol> <p>Alternatively, Yardarm works using this much shorter flow:</p> <ol> <li>Generate a syntax tree in memory directly from the OpenAPI specification</li> <li>Roslyn generates the compiled output from the syntax tree and writes to disk</li> </ol> <p>This avoids the expense of launching two separate processes, the expense of parsing/rendering text-based templates, and the expense of writing and then reading the code from disk followed by a C# parse. In exchange, we simply need to build the syntax tree ourselves.</p> <h2 id="extensibility">Extensibility</h2> <p>Earlier, I mentioned that a syntax tree, once built, is immutable. This is technically true but practically false. The classes and structures themselves are immutable once created, making the tree immutable, but a new tree can be created based the existing tree. Roslyn provides a variety of helper methods designed to make this easier such as <code class="language-plaintext highlighter-rouge">ReplaceNode</code>, <code class="language-plaintext highlighter-rouge">ReplaceNodes</code>, <code class="language-plaintext highlighter-rouge">Add...</code>, and <code class="language-plaintext highlighter-rouge">With...</code>.</p> <p>Yardarm uses this to its advantage, using a combination of the visitor pattern, aggregators, and dependency injection to allow easy customization of the generated code. I call the types “enrichers”, and their purpose is to enrich a particular syntax tree (or subset of a syntax tree) by examining it and either returning the original tree or creating a replacement. This is also a much more powerful tool for extensibility than typical template engine approaches, which often struggle with defining enough extension points or require replacing entire swaths of templates for a simple change.</p> <p>This approach is used extensively within the Yardarm internals in addition to allowing the injection of custom extensions. This provides better separation of concerns within the Yardarm source code. Here is an example built-in enricher which adds nullable annotations or default initialization to properties which represent request parameters. An <code class="language-plaintext highlighter-rouge">IOpenApiSyntaxNodeEnricher&lt;PropertyDeclarationSyntax, OpenApiParameter&gt;</code> is applied to nodes in the tree which are A) property declarations and B) were generated by a parameter definition on an OpenAPI request.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Microsoft.CodeAnalysis</span><span class="p">;</span> <span class="k">using</span> <span class="nn">Microsoft.CodeAnalysis.CSharp.Syntax</span><span class="p">;</span> <span class="k">using</span> <span class="nn">Microsoft.OpenApi.Interfaces</span><span class="p">;</span> <span class="k">using</span> <span class="nn">Microsoft.OpenApi.Models</span><span class="p">;</span> <span class="k">using</span> <span class="nn">Yardarm.Helpers</span><span class="p">;</span> <span class="k">using</span> <span class="nn">static</span> <span class="n">Microsoft</span><span class="p">.</span><span class="n">CodeAnalysis</span><span class="p">.</span><span class="n">CSharp</span><span class="p">.</span><span class="n">SyntaxFactory</span><span class="p">;</span> <span class="k">namespace</span> <span class="nn">Yardarm.Enrichment.Requests</span><span class="p">;</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">RequiredParameterEnricher</span> <span class="p">:</span> <span class="n">IOpenApiSyntaxNodeEnricher</span><span class="p">&lt;</span><span class="n">PropertyDeclarationSyntax</span><span class="p">,</span> <span class="n">OpenApiParameter</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="n">PropertyDeclarationSyntax</span> <span class="nf">Enrich</span><span class="p">(</span><span class="n">PropertyDeclarationSyntax</span> <span class="n">syntax</span><span class="p">,</span> <span class="n">OpenApiEnrichmentContext</span><span class="p">&lt;</span><span class="n">OpenApiParameter</span><span class="p">&gt;</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">context</span><span class="p">.</span><span class="n">Element</span><span class="p">.</span><span class="n">Required</span> <span class="p">||</span> <span class="n">context</span><span class="p">.</span><span class="n">Element</span><span class="p">.</span><span class="n">Schema</span><span class="p">.</span><span class="n">Nullable</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// If the parameter is optional OR nullable then the property should be nullable</span> <span class="c1">// This is because .NET does not have a good way to differentiate between missing and null</span> <span class="n">syntax</span> <span class="p">=</span> <span class="n">syntax</span><span class="p">.</span><span class="nf">MakeNullable</span><span class="p">();</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// The value needs to be initialized to avoid nullable ref type warnings</span> <span class="n">syntax</span> <span class="p">=</span> <span class="n">syntax</span><span class="p">.</span><span class="nf">MakeNullableOrInitializeIfReferenceType</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">Compilation</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">Element</span><span class="p">.</span><span class="n">Required</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Explicitly annotate as required if the parameter is required</span> <span class="n">syntax</span> <span class="p">=</span> <span class="nf">AddRequiredAttribute</span><span class="p">(</span><span class="n">syntax</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">syntax</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="n">PropertyDeclarationSyntax</span> <span class="nf">AddRequiredAttribute</span><span class="p">(</span><span class="n">PropertyDeclarationSyntax</span> <span class="n">syntax</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">syntax</span><span class="p">.</span><span class="nf">AddAttributeLists</span><span class="p">(</span> <span class="nf">AttributeList</span><span class="p">(</span><span class="nf">SingletonSeparatedList</span><span class="p">(</span> <span class="nf">Attribute</span><span class="p">(</span><span class="n">WellKnownTypes</span><span class="p">.</span><span class="n">System</span><span class="p">.</span><span class="n">ComponentModel</span><span class="p">.</span><span class="n">DataAnnotations</span><span class="p">.</span><span class="n">RequiredAttribute</span><span class="p">.</span><span class="n">Name</span><span class="p">)))</span> <span class="p">.</span><span class="nf">WithTrailingTrivia</span><span class="p">(</span><span class="n">ElasticCarriageReturnLineFeed</span><span class="p">));</span> <span class="p">}</span> </code></pre></div></div> <p>This class also uses several Yardarm-specific extension methods which perform some common actions on a syntax tree. Of particular interest is the <code class="language-plaintext highlighter-rouge">MakeNullableOrInitializeIfReferenceType</code> extension method, which makes use of the <code class="language-plaintext highlighter-rouge">SemanticModel</code>.</p> <p>The <code class="language-plaintext highlighter-rouge">SemanticModel</code> is an analysis of the syntax trees, referenced assemblies, etc to determine additional information which isn’t apparent from the syntax tree itself. In this example it is used to determine if the type of the property is a value type or a reference type as well as what constructors are available for that type.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span> <span class="c1">/// If the given property is a reference type and is not initialized, it should *either* be marked as nullable or</span> <span class="c1">/// be initialized to a non-null value. This method will do it's best to initialize the property using a default constructor</span> <span class="c1">/// or empty string, and failing that will mark the type as nullable.</span> <span class="c1">/// &lt;/summary&gt;</span> <span class="c1">/// &lt;param name="property"&gt;The &lt;see cref="PropertyDeclarationSyntax"/&gt; to update. Must be on a &lt;see cref="SyntaxTree"/&gt;.&lt;/param&gt;</span> <span class="c1">/// &lt;param name="semanticModel"&gt;&lt;see cref="SemanticModel"/&gt; used to perform type analysis.&lt;/param&gt;</span> <span class="c1">/// &lt;returns&gt;The mutated property declaration, or the original if no mutation was required.&lt;/returns&gt;</span> <span class="k">public</span> <span class="k">static</span> <span class="n">PropertyDeclarationSyntax</span> <span class="nf">MakeNullableOrInitializeIfReferenceType</span><span class="p">(</span><span class="k">this</span> <span class="n">PropertyDeclarationSyntax</span> <span class="n">property</span><span class="p">,</span> <span class="n">SemanticModel</span> <span class="n">semanticModel</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">property</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">property</span><span class="p">));</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">semanticModel</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">semanticModel</span><span class="p">));</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Initializer</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">||</span> <span class="n">property</span><span class="p">.</span><span class="n">ExpressionBody</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// No need if already initialized or expression body only</span> <span class="k">return</span> <span class="n">property</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Type</span> <span class="k">is</span> <span class="n">NullableTypeSyntax</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Already nullable</span> <span class="k">return</span> <span class="n">property</span><span class="p">;</span> <span class="p">}</span> <span class="kt">var</span> <span class="n">typeInfo</span> <span class="p">=</span> <span class="n">semanticModel</span><span class="p">.</span><span class="nf">GetTypeInfo</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Type</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">typeInfo</span><span class="p">.</span><span class="n">Type</span><span class="p">?.</span><span class="n">IsReferenceType</span> <span class="p">??</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">typeInfo</span><span class="p">.</span><span class="n">Type</span><span class="p">.</span><span class="n">SpecialType</span> <span class="p">==</span> <span class="n">SpecialType</span><span class="p">.</span><span class="n">System_String</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Initialize to an empty string</span> <span class="n">property</span> <span class="p">=</span> <span class="n">property</span> <span class="p">.</span><span class="nf">WithInitializer</span><span class="p">(</span><span class="nf">EqualsValueClause</span><span class="p">(</span><span class="n">SyntaxHelpers</span><span class="p">.</span><span class="nf">StringLiteral</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span> <span class="p">.</span><span class="nf">WithSemicolonToken</span><span class="p">(</span><span class="nf">Token</span><span class="p">(</span><span class="n">SyntaxKind</span><span class="p">.</span><span class="n">SemicolonToken</span><span class="p">));</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(!</span><span class="n">typeInfo</span><span class="p">.</span><span class="n">Type</span><span class="p">.</span><span class="n">IsAbstract</span> <span class="p">&amp;&amp;</span> <span class="n">typeInfo</span><span class="p">.</span><span class="n">Type</span><span class="p">.</span><span class="nf">GetMembers</span><span class="p">()</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Kind</span> <span class="p">==</span> <span class="n">SymbolKind</span><span class="p">.</span><span class="n">Method</span> <span class="p">&amp;&amp;</span> <span class="n">p</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">".ctor"</span><span class="p">)</span> <span class="p">.</span><span class="n">Cast</span><span class="p">&lt;</span><span class="n">IMethodSymbol</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">Any</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="n">Length</span> <span class="p">==</span> <span class="m">0</span> <span class="p">&amp;&amp;</span> <span class="n">p</span><span class="p">.</span><span class="n">DeclaredAccessibility</span> <span class="p">==</span> <span class="n">Accessibility</span><span class="p">.</span><span class="n">Public</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// Build a default object using the default constructor</span> <span class="n">property</span> <span class="p">=</span> <span class="n">property</span> <span class="p">.</span><span class="nf">WithInitializer</span><span class="p">(</span><span class="nf">EqualsValueClause</span><span class="p">(</span><span class="nf">ObjectCreationExpression</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Type</span><span class="p">)))</span> <span class="p">.</span><span class="nf">WithSemicolonToken</span><span class="p">(</span><span class="nf">Token</span><span class="p">(</span><span class="n">SyntaxKind</span><span class="p">.</span><span class="n">SemicolonToken</span><span class="p">));</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// Mark the types as nullable, even if the parameter is required</span> <span class="c1">// This will encourage SDK consumers to check for nulls and prevent NREs</span> <span class="n">property</span> <span class="p">=</span> <span class="n">property</span><span class="p">.</span><span class="nf">MakeNullable</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="n">property</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <h3 id="extensibility-and-json-serialization">Extensibility and JSON Serialization</h3> <p>There is a lot of upheaval in the JSON space in .NET since the arrival of System.Text.Json. Newtonsoft.Json has been the go-to serializer for years and is very feature rich, but System.Text.Json is maintained by Microsoft and offers some significant performance benefits. As a result, I wanted Yardarm to have robust support for both of these options (or any other option someone may prefer).</p> <p>Therefore, JSON serialization within Yardarm is provided as an extension to the core Yardarm implementation. When generating an SDK, simply add one of the two extensions and they add all necessary annotation attributes and wire themselves up as the serializer for <code class="language-plaintext highlighter-rouge">application/json</code> and other related content types.</p> <p>This also means that support for other formats, such as XML, could be added via extensions.</p> <h2 id="embedded-source-code">Embedded Source Code</h2> <p>Visual Studio also offers a great set of features around debugging third-party DLL files. One such feature is <a href="https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink">SourceLink</a>, which links the compiled code back to lines in the original source files on GitHub, GitLab, etc. However, since Yardarm generates code directly using Roslyn, how can we provide the source code with the SDK for debugging purposes?</p> <p>The answer: we directly embed the source in the PDB file (optionally, of course). However, the C# source code generated doesn’t tend to be very legible. We’re generating the syntax tree without all the nice things like, you know, whitespace.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">publicstringMyMethod</span><span class="p">(){</span><span class="n">returncodethatlookslikethis</span><span class="p">+</span><span class="n">isTERRIBLYhardtoread</span><span class="p">;}</span> </code></pre></div></div> <p>Yardarm addresses this using another enricher, run only if source embedding is enabled, which applies formatting. It uses <code class="language-plaintext highlighter-rouge">Formatter.FormatAsync</code> to do the heavy lifting, which is (I believe) the formatter used internally by Visual Studio when you autoformat a file.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">FormatCompilationEnricher</span> <span class="p">:</span> <span class="n">ICompilationEnricher</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">YardarmGenerationSettings</span> <span class="n">_settings</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">FormatCompilationEnricher</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span> <span class="k">public</span> <span class="n">Type</span><span class="p">[]</span> <span class="n">ExecuteAfter</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="p">{</span> <span class="k">typeof</span><span class="p">(</span><span class="n">VersionAssemblyInfoEnricher</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">SyntaxTreeCompilationEnricher</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">DefaultTypeSerializersEnricher</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">OpenApiCompilationEnricher</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ResourceFileCompilationEnricher</span><span class="p">)</span> <span class="p">};</span> <span class="k">public</span> <span class="nf">FormatCompilationEnricher</span><span class="p">(</span><span class="n">YardarmGenerationSettings</span> <span class="n">settings</span><span class="p">,</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">FormatCompilationEnricher</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span> <span class="p">{</span> <span class="n">ArgumentNullException</span><span class="p">.</span><span class="nf">ThrowIfNull</span><span class="p">(</span><span class="n">settings</span><span class="p">);</span> <span class="n">ArgumentNullException</span><span class="p">.</span><span class="nf">ThrowIfNull</span><span class="p">(</span><span class="n">logger</span><span class="p">);</span> <span class="n">_settings</span> <span class="p">=</span> <span class="n">settings</span><span class="p">;</span> <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">ValueTask</span><span class="p">&lt;</span><span class="n">CSharpCompilation</span><span class="p">&gt;</span> <span class="nf">EnrichAsync</span><span class="p">(</span><span class="n">CSharpCompilation</span> <span class="n">target</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">_settings</span><span class="p">.</span><span class="n">EmbedAllSources</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Don't bother formatting if we're not embedding source</span> <span class="k">return</span> <span class="n">target</span><span class="p">;</span> <span class="p">}</span> <span class="kt">var</span> <span class="n">stopwatch</span> <span class="p">=</span> <span class="n">Stopwatch</span><span class="p">.</span><span class="nf">StartNew</span><span class="p">();</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">workspace</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AdhocWorkspace</span><span class="p">();</span> <span class="kt">var</span> <span class="n">solution</span> <span class="p">=</span> <span class="n">workspace</span> <span class="p">.</span><span class="nf">AddSolution</span><span class="p">(</span> <span class="n">SolutionInfo</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span> <span class="n">SolutionId</span><span class="p">.</span><span class="nf">CreateNewId</span><span class="p">(</span><span class="n">_settings</span><span class="p">.</span><span class="n">AssemblyName</span><span class="p">),</span> <span class="n">VersionStamp</span><span class="p">.</span><span class="n">Default</span><span class="p">));</span> <span class="n">Project</span> <span class="n">project</span> <span class="p">=</span> <span class="n">solution</span><span class="p">.</span><span class="nf">AddProject</span><span class="p">(</span><span class="n">_settings</span><span class="p">.</span><span class="n">AssemblyName</span><span class="p">,</span> <span class="n">_settings</span><span class="p">.</span><span class="n">AssemblyName</span> <span class="p">+</span> <span class="s">".dll"</span><span class="p">,</span> <span class="n">LanguageNames</span><span class="p">.</span><span class="n">CSharp</span><span class="p">);</span> <span class="n">workspace</span><span class="p">.</span><span class="nf">TryApplyChanges</span><span class="p">(</span><span class="n">solution</span><span class="p">);</span> <span class="c1">// Exclude files with no path (won't be embedded)</span> <span class="c1">// We still format resource files, which are typically already formatted, because they may have</span> <span class="c1">// been mutated by other enrichers.</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">SyntaxTree</span><span class="p">&gt;</span> <span class="n">treesToBeFormatted</span> <span class="p">=</span> <span class="n">target</span><span class="p">.</span><span class="n">SyntaxTrees</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="k">static</span> <span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">FilePath</span> <span class="p">!=</span> <span class="s">""</span> <span class="p">&amp;&amp;</span> <span class="n">p</span><span class="p">.</span><span class="n">HasCompilationUnitRoot</span><span class="p">);</span> <span class="c1">// Process formatting in parallel, this gives a slight perf boost</span> <span class="kt">object</span> <span class="n">lockObj</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span> <span class="k">await</span> <span class="n">Parallel</span><span class="p">.</span><span class="nf">ForEachAsync</span><span class="p">(</span><span class="n">treesToBeFormatted</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="n">syntaxTree</span><span class="p">,</span> <span class="n">localCt</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="n">SyntaxNode</span> <span class="n">root</span> <span class="p">=</span> <span class="k">await</span> <span class="n">syntaxTree</span><span class="p">.</span><span class="nf">GetRootAsync</span><span class="p">(</span><span class="n">localCt</span><span class="p">);</span> <span class="n">Document</span> <span class="n">document</span> <span class="p">=</span> <span class="n">project</span><span class="p">.</span><span class="nf">AddDocument</span><span class="p">(</span><span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">().</span><span class="nf">ToString</span><span class="p">(),</span> <span class="n">root</span><span class="p">);</span> <span class="n">document</span> <span class="p">=</span> <span class="k">await</span> <span class="n">Formatter</span><span class="p">.</span><span class="nf">FormatAsync</span><span class="p">(</span><span class="n">document</span><span class="p">,</span> <span class="n">solution</span><span class="p">.</span><span class="n">Options</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span> <span class="n">SyntaxNode</span><span class="p">?</span> <span class="n">newRoot</span> <span class="p">=</span> <span class="k">await</span> <span class="n">document</span><span class="p">.</span><span class="nf">GetSyntaxRootAsync</span><span class="p">(</span><span class="n">localCt</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">newRoot</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">newRoot</span> <span class="p">!=</span> <span class="n">root</span><span class="p">)</span> <span class="p">{</span> <span class="k">lock</span> <span class="p">(</span><span class="n">lockObj</span><span class="p">)</span> <span class="p">{</span> <span class="n">target</span> <span class="p">=</span> <span class="n">target</span><span class="p">.</span><span class="nf">ReplaceSyntaxTree</span><span class="p">(</span><span class="n">syntaxTree</span><span class="p">,</span> <span class="n">syntaxTree</span><span class="p">.</span><span class="nf">WithRootAndOptions</span><span class="p">(</span><span class="n">newRoot</span><span class="p">,</span> <span class="n">syntaxTree</span><span class="p">.</span><span class="n">Options</span><span class="p">));</span> <span class="p">}</span> <span class="p">}</span> <span class="p">});</span> <span class="n">stopwatch</span><span class="p">.</span><span class="nf">Stop</span><span class="p">();</span> <span class="n">_logger</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">"Sources formatted for embedding in {elapsed}ms"</span><span class="p">,</span> <span class="n">stopwatch</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">);</span> <span class="k">return</span> <span class="n">target</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="other-functionality">Other Functionality</h2> <p>Yardarm has a LOT of other functionality. Too much to cover in this blog post, which focuses on the Roslyn aspects of Yardarm. However, I’ll try to list a few of the key points here.</p> <ul> <li>Allows targeting many variants of .NET, including .NET 6 and 7 <ul> <li>.NET Framework 4.6.1 and later is supported via .NET Standard 2.0</li> </ul> </li> <li>Generates DLLs, PDB debug files, XML documentation files, and reference assemblies <ul> <li>XML documentation is extracted from the documentation in the OpenAPI specification</li> </ul> </li> <li>Directly generates a NuGet package which includes the above files, including support for multi-targeting</li> <li>Uses many modern compiler features <ul> <li>For example, when targeting .NET 6 it will use string interpolation handlers for more efficient string building</li> </ul> </li> <li>Supports many great patterns and practices <ul> <li>Includes a built-in extension which supports DI registration using <code class="language-plaintext highlighter-rouge">HttpClientFactory</code></li> <li>Generated SDKs are asynchronous from top to bottom</li> <li>Makes use of polymorphism to handle the complexities of requests, responses, and discriminated schemas in a way that (usually) won’t break over time as the specification changes</li> <li>Interfaces are generated to make mocking for unit tests easy</li> </ul> </li> <li>Available as a .NET Global Tool, a Docker image, or an MSBuild SDK <ul> <li>The <a href="https://github.com/CenterEdge/Yardarm/blob/main/docs/generating/yardarm-sdk.md">MSBuild SDK</a> approach is particularly cool, you can build SDKs as a project in your solution</li> </ul> </li> <li>Automatically uses NuGet to download dependencies for compilation, such as <code class="language-plaintext highlighter-rouge">Newtonsoft.Json</code> or any other dependency defined by an extension <ul> <li>Yardarm even executes any Roslyn 4 source generators included in the dependencies</li> </ul> </li> </ul> <h2 id="future-work">Future Work</h2> <p>Yardarm is in use in production, but is still a work in progress. Future plans include:</p> <ul> <li>Extensions for serializing/deserializing dates and times using <a href="https://nodatime.org/">NodaTime</a></li> <li>Refactor System.Text.Json polymorphism to use the new built-in support in .NET 7</li> <li>Support for link-level trimming when used with System.Text.Json</li> </ul> <p>There are also doubtless API specifications in the wild with use cases which don’t work well today. I’d love to hear about them in <a href="https://github.com/CenterEdge/Yardarm/issues">Issues</a> so Yardarm can continue to improve.</p> <h2 id="conclusion">Conclusion</h2> <p>Hopefully Yardarm showcases the power and flexibility of Roslyn, above and beyond basic compilation and cool code refactoring in Visual Studio. Version 0.3.0 is available and ready for use, and I look forward to feedback from the .NET community on the project.</p>Brant BurnettThis blog is one of The December 9th entries on the 2022 C# Advent Calendar. Believe it or not, this is my 6th year participating. Thanks for having me again Matt and Calvin!String Interpolation Trickery and Magic with C# 10 and .NET 62021-12-17T11:00:00+00:002021-12-17T11:00:00+00:00https://btburnett.com/csharp/2021/12/17/string-interpolation-trickery<p class="notice--info">This blog is one of The December 17th entries on the <a href="https://www.csadvent.christmas/">2021 C# Advent Calendar</a>. Thanks for having me again Matt!</p> <p>For the last few years we’ve gotten a new version of C# paired with a new version of .NET each November. And every year this new version is packed with great new features. For me, one of the coolest features is <a href="https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/">interpolated string handlers</a>.</p> <p>Interpolated string handlers are primarily designed to provide a performance boost building strings. But is there more to them than meets the eye? I believe that they lay the groundwork for doing much more than just building strings faster.</p> <h2 id="interpolated-string-handler-overview">Interpolated String Handler Overview</h2> <p>First, let’s start with an overview of how interpolated string handlers work. For a more in-depth look, see the <a href="https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/">blog post from Stephen Toub</a>.</p> <p>When using C# 9, interpolating a string is optimized by the compiler in a variety of ways. However, in many cases the optimizations aren’t an option, and a call to <code class="language-plaintext highlighter-rouge">string.Format(...)</code> is used. <code class="language-plaintext highlighter-rouge">string.Format</code> brings a lot of overhead, such as interpreting the format string every call, potentially allocating an <code class="language-plaintext highlighter-rouge">object[]</code> on the heap, boxing value types, and generating temporary intermediate strings.</p> <p>For projects targeting .NET 6, even upgrading existing projects, string interpolation gets an immediate performance boost because they will use the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.defaultinterpolatedstringhandler?view=net-6.0">DefaultInterpolatedStringHandler</a> to build strings. This structure has a much better performance profile overall than <code class="language-plaintext highlighter-rouge">string.Format</code>.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Example code, from Stephen Toub's post</span> <span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">FormatVersion</span><span class="p">(</span><span class="kt">int</span> <span class="n">major</span><span class="p">,</span> <span class="kt">int</span> <span class="n">minor</span><span class="p">,</span> <span class="kt">int</span> <span class="n">build</span><span class="p">,</span> <span class="kt">int</span> <span class="n">revision</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="n">major</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">minor</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">build</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">revision</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> </code></pre></div></div> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Example equivalent code the compiler generates with .NET 6, from Stephen Toub's post</span> <span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">FormatVersion</span><span class="p">(</span><span class="kt">int</span> <span class="n">major</span><span class="p">,</span> <span class="kt">int</span> <span class="n">minor</span><span class="p">,</span> <span class="kt">int</span> <span class="n">build</span><span class="p">,</span> <span class="kt">int</span> <span class="n">revision</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">handler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DefaultInterpolatedStringHandler</span><span class="p">(</span><span class="n">literalLength</span><span class="p">:</span> <span class="m">3</span><span class="p">,</span> <span class="n">formattedCount</span><span class="p">:</span> <span class="m">4</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">major</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">minor</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">build</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">revision</span><span class="p">);</span> <span class="k">return</span> <span class="n">handler</span><span class="p">.</span><span class="nf">ToStringAndClear</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <h2 id="custom-interpolated-string-handlers">Custom Interpolated String Handlers</h2> <p>The <code class="language-plaintext highlighter-rouge">DefaultInterpolatedStringHandler</code> is really just the beginning, though. Methods which accept a string may have an overload which accepts a custom interpolated string handler. When present, this causes C# to use the custom handler you define rather than the default, allowing more advanced behaviors.</p> <p>These behaviors can include stack allocations of scratch space, accepting other parameters to the method call as constructor arguments to change behaviors, returning a boolean from the constructor that skips the <code class="language-plaintext highlighter-rouge">AppendXXX</code> steps if we know they’ll be unused, or short-circuiting additional <code class="language-plaintext highlighter-rouge">AppendXXX</code> steps if we want to stop early.</p> <p>A great example is <code class="language-plaintext highlighter-rouge">AssertInterpolatedStringHandler</code> which is available on <code class="language-plaintext highlighter-rouge">Debug.Assert</code> calls. It can suppress most of the work building the string in the case where the first parameter to the Assert call is <code class="language-plaintext highlighter-rouge">true</code>.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">Example</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">count</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="c1">// The interpolated string below will never be constructed in .NET 6, even when compiled in DEBUG mode</span> <span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">count</span> <span class="p">==</span> <span class="m">0</span><span class="p">,</span> <span class="s">$"The count should be 0, but is </span><span class="p">{</span><span class="n">count</span><span class="p">}</span><span class="s">."</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>The equivalent code for the above statement is something like:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">Example</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">count</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="kt">var</span> <span class="n">condition</span> <span class="p">=</span> <span class="n">count</span> <span class="p">==</span> <span class="m">0</span><span class="p">;</span> <span class="kt">var</span> <span class="n">handler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AssertInterpolatedStringHandler</span><span class="p">(</span><span class="m">31</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="n">condition</span><span class="p">,</span> <span class="k">out</span> <span class="kt">bool</span> <span class="n">shouldAppend</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">shouldAppend</span><span class="p">)</span> <span class="p">{</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"The count should be 0, but is "</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">count</span><span class="p">);</span> <span class="n">handler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span> <span class="p">}</span> <span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">condition</span><span class="p">,</span> <span class="n">handler</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>If the <code class="language-plaintext highlighter-rouge">AppendXXX</code> methods return <code class="language-plaintext highlighter-rouge">bool</code> the result is checked after each call and can short-circuit part way through the operation. For example, this might occur if the destination buffer runs out of space.</p> <h2 id="now-bring-on-the-magic">Now Bring On The Magic</h2> <p>Now, I’d like you to take a moment and consider the <code class="language-plaintext highlighter-rouge">AssertInterpolatedStringHandler</code> example above. What does the example have to do with strings? At what point are strings involved?</p> <p>Wait for it…</p> <p>The answer is “Only the literal segments are strings.” Of course, <code class="language-plaintext highlighter-rouge">Debug.Assert</code> ends up making a string out of it by calling <code class="language-plaintext highlighter-rouge">handler.ToStringAndClear();</code> But the <code class="language-plaintext highlighter-rouge">AssertInterpolatedStringHandler</code> is what’s passed to <code class="language-plaintext highlighter-rouge">Debug.Assert</code>, not a string. It can do whatever it likes with the handler. Additionally, the implication of <code class="language-plaintext highlighter-rouge">AppendFormatted</code> is that it will format <code class="language-plaintext highlighter-rouge">count</code> as a string and append it. But in reality it may do whatever it likes.</p> <p>Therefore, if we can imagine a “thing” which is built up of string literals and expressions presented in order, then we can build it using an interpolated string. Even if what we’re creating isn’t a string at all.</p> <h2 id="an-example-parameterized-sql-queries">An Example: Parameterized SQL Queries</h2> <p>Have you ever needed to build a parameterized SQL query? Or <a href="https://www.couchbase.com/products/n1ql">N1QL Query</a> if you’re a <a href="https://www.couchbase.com/">Couchbase</a> user? It’s very important to parameterize user input to prevent injection attacks, so the answer for most database users should be yes. Even if you use an ODM or ORM like Entity Framework it’s often necessary to hand-write queries for special cases.</p> <p>Building a parameterized query can be a pain. Take this relatively simple example for <code class="language-plaintext highlighter-rouge">SqlCommand</code>:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="nf">GetOptionValue</span><span class="p">(</span><span class="kt">int</span> <span class="n">optionSet</span><span class="p">,</span> <span class="kt">string</span> <span class="n">optionName</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">cmd</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlCommand</span><span class="p">(</span><span class="s">"SELECT OptionValue FROM Options WHERE OptionSet = @OptionSet AND OptionName = @OptionName"</span><span class="p">,</span> <span class="n">_connection</span><span class="p">);</span> <span class="n">cmd</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"@OptionSet"</span><span class="p">,</span> <span class="n">SqlDbType</span><span class="p">.</span><span class="n">Int</span><span class="p">).</span><span class="n">Value</span> <span class="p">=</span> <span class="n">optionSet</span><span class="p">;</span> <span class="n">cmd</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"@OptionName"</span><span class="p">,</span> <span class="n">SqlDbType</span><span class="p">.</span><span class="n">NVarChar</span><span class="p">).</span><span class="n">Value</span> <span class="p">=</span> <span class="n">optionName</span><span class="p">;</span> <span class="k">return</span> <span class="p">(</span><span class="k">await</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">ExecuteScalarAsync</span><span class="p">()).</span><span class="nf">ToString</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <p>If you hate the duplication involved in the parameter names, you may even pull those out to constants, which is even more convoluted.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">OptionSetParamName</span> <span class="p">=</span> <span class="s">"@OptionSet"</span><span class="p">;</span> <span class="k">private</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">OptionNameParamName</span> <span class="p">=</span> <span class="s">"@OptionName"</span><span class="p">;</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="nf">GetOptionValue</span><span class="p">(</span><span class="kt">int</span> <span class="n">optionSet</span><span class="p">,</span> <span class="kt">string</span> <span class="n">optionName</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">cmd</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlCommand</span><span class="p">(</span><span class="s">$"SELECT OptionValue FROM Options WHERE OptionSet = </span><span class="p">{</span><span class="n">OptionSetParamName</span><span class="p">}</span><span class="s"> AND OptionName = </span><span class="p">{</span><span class="n">OptionNameParamName</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="n">_connection</span><span class="p">);</span> <span class="n">cmd</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">OptionSetParamName</span><span class="p">,</span> <span class="n">SqlDbType</span><span class="p">.</span><span class="n">Int</span><span class="p">).</span><span class="n">Value</span> <span class="p">=</span> <span class="n">optionSet</span><span class="p">;</span> <span class="n">cmd</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">OptionNameParamName</span><span class="p">,</span> <span class="n">SqlDbType</span><span class="p">.</span><span class="n">NVarChar</span><span class="p">).</span><span class="n">Value</span> <span class="p">=</span> <span class="n">optionName</span><span class="p">;</span> <span class="k">return</span> <span class="p">(</span><span class="k">await</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">ExecuteScalarAsync</span><span class="p">()).</span><span class="nf">ToString</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <p>What if this could be written instead as follows, but still retained all the security of parameterized queries?</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="nf">GetOptionValue</span><span class="p">(</span><span class="kt">int</span> <span class="n">optionSet</span><span class="p">,</span> <span class="kt">string</span> <span class="n">optionName</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="k">using</span> <span class="nn">cmd</span> <span class="p">=</span> <span class="n">_connection</span><span class="p">.</span><span class="nf">CreateCommand</span><span class="p">(</span><span class="s">$"SELECT OptionValue FROM Options WHERE OptionSet = </span><span class="p">{</span><span class="n">optionSet</span><span class="p">}</span><span class="s"> AND OptionName = </span><span class="p">{</span><span class="n">optionName</span><span class="p">}</span><span class="s">"</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span><span class="k">await</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">ExecuteScalarAsync</span><span class="p">()).</span><span class="nf">ToString</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <h2 id="example-implementation">Example Implementation</h2> <p>This example gets somewhat complicated, so I’ve tried to annotate it throughout with comments. First, the builder itself.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// This attribute let's C# know we're making an interpolated string handler</span> <span class="p">[</span><span class="n">InterpolatedStringHandler</span><span class="p">]</span> <span class="c1">// The handler should usually be a "ref struct", meaning it only lives on the stack.</span> <span class="c1">// This may be a limitation if you want to allow "await" within the holes in the expression, so "ref" may be removed in that case.</span> <span class="c1">// However, this example requires "ref struct" because it includes a DefaultInterpolatedStringHandler in its fields.</span> <span class="k">public</span> <span class="k">ref</span> <span class="k">struct</span> <span class="nc">SqlCommandInterpolatedStringHandler</span> <span class="p">{</span> <span class="c1">// Internally we'll use DefaultInterpolatedStringHandler to build the query string.</span> <span class="c1">// This be more performant than reinventing the wheel.</span> <span class="k">private</span> <span class="n">DefaultInterpolatedStringHandler</span> <span class="n">_innerHandler</span><span class="p">;</span> <span class="c1">// This will maintain a list of parameters as we build the query string</span> <span class="k">public</span> <span class="n">SqlParameter</span><span class="p">[]</span> <span class="n">Parameters</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// The number of parameters added so far</span> <span class="k">private</span> <span class="kt">int</span> <span class="n">_parameterCount</span><span class="p">;</span> <span class="k">public</span> <span class="nf">SqlCommandInterpolatedStringHandler</span><span class="p">(</span><span class="kt">int</span> <span class="n">literalLength</span><span class="p">,</span> <span class="kt">int</span> <span class="n">formattedCount</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Construct the inner handler, forwarding the same hints</span> <span class="n">_innerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DefaultInterpolatedStringHandler</span><span class="p">(</span><span class="n">literalLength</span><span class="p">,</span> <span class="n">formattedCount</span><span class="p">);</span> <span class="c1">// Build an empty list of parameters with the capacity we'll need</span> <span class="n">Parameters</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SqlParameter</span><span class="p">[</span><span class="n">formattedCount</span><span class="p">];</span> <span class="n">_parameterCount</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">AppendLiteral</span><span class="p">(</span><span class="kt">string</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="c1">// Forward literals to the inner handler to be added to the query string</span> <span class="c1">// In this example, literals represent query text like "SELECT ..."</span> <span class="n">_innerHandler</span><span class="p">.</span><span class="nf">AppendLiteral</span><span class="p">(</span><span class="k">value</span><span class="p">);</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="c1">// SqlParameters need strings not char spans, so forward to that implementation</span> <span class="c1">// Other backing implementations may be able to optimize this to avoid allocating a string</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="k">value</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span> <span class="k">public</span> <span class="k">void</span> <span class="n">AppendFormatted</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">switch</span> <span class="p">(</span><span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">case</span> <span class="kt">int</span> <span class="n">intValue</span><span class="p">:</span> <span class="nf">AppendParameter</span><span class="p">(</span><span class="n">SqlDbType</span><span class="p">.</span><span class="n">Int</span><span class="p">,</span> <span class="n">intValue</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span> <span class="k">case</span> <span class="kt">bool</span> <span class="n">boolValue</span><span class="p">:</span> <span class="nf">AppendParameter</span><span class="p">(</span><span class="n">SqlDbType</span><span class="p">.</span><span class="n">Bit</span><span class="p">,</span> <span class="n">boolValue</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span> <span class="k">case</span> <span class="kt">string</span> <span class="n">stringValue</span><span class="p">:</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">stringValue</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span> <span class="c1">// Add support for more types here</span> <span class="k">default</span><span class="p">:</span> <span class="c1">// Fallback for other types, we could make this smarter or throw an exception</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="k">value</span><span class="p">?.</span><span class="nf">ToString</span><span class="p">());</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// There are a lot of AppendFormatted overloads we're required to implement</span> <span class="c1">// We could use alignment and format parameters for our own purposes, here we ignore them</span> <span class="k">public</span> <span class="k">void</span> <span class="n">AppendFormatted</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="kt">string</span><span class="p">?</span> <span class="n">format</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="k">value</span><span class="p">);</span> <span class="k">public</span> <span class="k">void</span> <span class="n">AppendFormatted</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="kt">int</span> <span class="n">alignment</span><span class="p">,</span> <span class="kt">string</span><span class="p">?</span> <span class="n">format</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="k">value</span><span class="p">);</span> <span class="k">public</span> <span class="k">void</span> <span class="n">AppendFormatted</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="kt">int</span> <span class="n">alignment</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="k">value</span><span class="p">);</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="kt">string</span><span class="p">?</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">AppendParameter</span><span class="p">(</span><span class="n">SqlDbType</span><span class="p">.</span><span class="n">NVarChar</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">AppendFormatted</span><span class="p">(</span><span class="kt">string</span><span class="p">?</span> <span class="k">value</span><span class="p">,</span> <span class="kt">int</span> <span class="n">alignment</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span> <span class="kt">string</span><span class="p">?</span> <span class="n">format</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nf">AppendParameter</span><span class="p">(</span><span class="n">SqlDbType</span><span class="p">.</span><span class="n">NVarChar</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="c1">// Main handler for formatted segments</span> <span class="k">private</span> <span class="k">void</span> <span class="nf">AppendParameter</span><span class="p">(</span><span class="n">SqlDbType</span> <span class="n">paramType</span><span class="p">,</span> <span class="kt">object</span><span class="p">?</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Since this is intended for use from compiler-generated code, we'll leave out typical runtime</span> <span class="c1">// preconditions like _parameterCount vs array length. We'll use Debug.Assert instead, and assume</span> <span class="c1">// the compiler used the type correctly for release builds.</span> <span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">_parameterCount</span> <span class="p">&lt;</span> <span class="n">Parameters</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="s">"Exceeded formattedCount"</span><span class="p">);</span> <span class="c1">// Create a unique parameter name, use an interpolated string builder with a stack-allocated buffer</span> <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">paramNameBuffer</span> <span class="p">=</span> <span class="k">stackalloc</span> <span class="kt">char</span><span class="p">[</span><span class="m">8</span><span class="p">];</span> <span class="kt">var</span> <span class="n">paramName</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="n">paramNameBuffer</span><span class="p">,</span> <span class="s">$"@Param</span><span class="p">{</span><span class="n">_parameterCount</span><span class="p">}</span><span class="s">"</span><span class="p">);</span> <span class="c1">// Add the parameter name reference to the query string</span> <span class="n">_innerHandler</span><span class="p">.</span><span class="nf">AppendFormatted</span><span class="p">(</span><span class="n">paramName</span><span class="p">);</span> <span class="c1">// Add the parameter to the collection</span> <span class="n">Parameters</span><span class="p">[</span><span class="n">_parameterCount</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlParameter</span><span class="p">(</span><span class="n">paramName</span><span class="p">,</span> <span class="n">paramType</span><span class="p">)</span> <span class="p">{</span> <span class="n">Value</span> <span class="p">=</span> <span class="k">value</span> <span class="p">};</span> <span class="n">_parameterCount</span><span class="p">++;</span> <span class="p">}</span> <span class="c1">// Forward to the inner handler</span> <span class="k">public</span> <span class="k">readonly</span> <span class="k">override</span> <span class="kt">string</span> <span class="nf">ToString</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_innerHandler</span><span class="p">.</span><span class="nf">ToString</span><span class="p">();</span> <span class="c1">// Forward to the inner handler</span> <span class="k">public</span> <span class="kt">string</span> <span class="nf">ToStringAndClear</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_innerHandler</span><span class="p">.</span><span class="nf">ToStringAndClear</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <p>This builder can then be invoked using this extension method:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">SqlConnectionExtensions</span> <span class="p">{</span> <span class="k">public</span> <span class="k">static</span> <span class="n">SqlCommand</span> <span class="nf">CreateCommand</span><span class="p">(</span><span class="k">this</span> <span class="n">SqlConnection</span> <span class="n">connection</span><span class="p">,</span> <span class="c1">// The handler should be the last argument.</span> <span class="c1">// Where possible (i.e. non-async methods) it should usually be a by-ref argument.</span> <span class="k">ref</span> <span class="n">SqlCommandInterpolatedStringHandler</span> <span class="n">handler</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// We must use ToStringAndClear(), not ToString(), to ensure we release resources</span> <span class="kt">var</span> <span class="n">commandText</span> <span class="p">=</span> <span class="n">handler</span><span class="p">.</span><span class="nf">ToStringAndClear</span><span class="p">();</span> <span class="c1">// Create the command and add the parameters stored in the handler</span> <span class="kt">var</span> <span class="n">cmd</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlCommand</span><span class="p">(</span><span class="n">commandText</span><span class="p">,</span> <span class="n">connection</span><span class="p">);</span> <span class="n">cmd</span><span class="p">.</span><span class="n">Parameters</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="n">handler</span><span class="p">.</span><span class="n">Parameters</span><span class="p">);</span> <span class="k">return</span> <span class="n">cmd</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>The generated SQL command text looks like this:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">OptionValue</span> <span class="k">FROM</span> <span class="k">Options</span> <span class="k">WHERE</span> <span class="n">OptionSet</span> <span class="o">=</span> <span class="o">@</span><span class="n">Param0</span> <span class="k">AND</span> <span class="n">OptionName</span> <span class="o">=</span> <span class="o">@</span><span class="n">Param1</span> </code></pre></div></div> <p>It’s that easy! Okay, maybe not quite easy, but still very powerful. Of course, there’s also a lot of room for improvement on this quick example.</p> <ul> <li>The parameter array could come from the array pool, though I’d want to measure that with benchmarks to be sure it’s advantageous</li> <li>An overload that takes a stack-allocated <code class="language-plaintext highlighter-rouge">Span&lt;SqlParameter&gt;</code> (probably overkill given all the other heap allocations and boxing related to <code class="language-plaintext highlighter-rouge">SqlParameter</code>)</li> <li>A pre-built, static set of parameter names for reuse</li> <li>Support for more parameter types</li> <li>Using format strings to specify parameter types, i.e. varchar vs nvarchar</li> <li>Another static method to create and execute the command rather than just create it</li> <li>And probably much more I haven’t considered</li> </ul> <h2 id="other-random-ideas">Other Random Ideas</h2> <p>Here are a few random ideas. Some of these ideas are probably be silly in practice. I’m hoping they’ll get everyone’s creative juices flowing.</p> <p>Please, don’t write back telling me how dumb the ideas are :). If you have any other ideas, I’d love to see them in the comments!</p> <h3 id="random-idea-1-json-building-without-pocos">Random Idea #1: JSON Building Without POCOs</h3> <p>This idea is interesting, but the double curly braces get a bit gnarly. Also, in most cases you probably want a POCO, but I can see simple scenarios where POCOs are overkill. This example also assumes that the literals are parsed at runtime to add double quotes around attribute names. This probably doesn’t have great performance, so it’s more of a thought experiment, but who knows.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">GetPersonJson</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">int</span> <span class="n">age</span><span class="p">,</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Child</span><span class="p">&gt;</span> <span class="n">children</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// name will get wrapped in quotes and special characters escaped, age would be a plain number,</span> <span class="c1">// children gets serialized as an array</span> <span class="k">return</span> <span class="n">JsonInterpolatedSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="s">$"</span><span class="p">{{</span><span class="n">name</span><span class="p">:{</span><span class="n">name</span><span class="p">},</span><span class="n">age</span><span class="p">:{</span><span class="n">age</span><span class="p">},</span><span class="n">children</span><span class="p">:{</span><span class="n">children</span><span class="p">}}}</span><span class="s">"</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <h3 id="random-idea-2-safely-building-html">Random Idea #2: Safely Building HTML</h3> <p>In most cases, we build HTML in Razor views or pages. However, sometimes we need to build HTML in code. This can be done with a TagBuilder, but that can feel unwieldy. Building a string that just applies appropriate escaping would be nice. It could even use a TagBuilder under the hood. Though it probably wouldn’t be quite as performant given that the literals would need parsing.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">IHtmlContent</span> <span class="nf">CreateParagraph</span><span class="p">(</span><span class="kt">string</span> <span class="n">content</span><span class="p">,</span> <span class="kt">string</span> <span class="k">class</span><span class="err">)</span> <span class="err">{</span> <span class="c1">// name will get wrapped in quotes and special characters escaped, age would be a plain number,</span> <span class="c1">// children gets serialized as an array</span> <span class="nc">return</span> <span class="n">HtmlInterpolatedBuilder</span><span class="p">.</span><span class="nf">Build</span><span class="p">(</span><span class="s">$"&lt;p class=</span><span class="p">{</span><span class="k">class</span><span class="err">}&gt;{</span><span class="nc">content</span><span class="p">}</span><span class="s">&lt;/p&gt;"</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <h3 id="random-other-idea-3-hash-codes">Random Other Idea #3: Hash Codes</h3> <p>This is an example that doesn’t involve strings at all. When overriding <code class="language-plaintext highlighter-rouge">object.Equals(object other)</code> its generally accepted that you should also override <code class="language-plaintext highlighter-rouge">object.GetHashCode()</code>. However, calculating a hash code for your object may be cumbersome, especially if there are a lot of fields. <a href="https://docs.microsoft.com/en-us/dotnet/api/system.hashcode?view=net-6.0">System.HashCode</a> makes this process easier, but still uses a lot of boilerplate.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">GetHashCode</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// I know, I know, there's also HashCode.Combine&lt;...&gt;(...)</span> <span class="c1">// But that method can't override the comparer, and is limited to 8 fields.</span> <span class="kt">var</span> <span class="n">hash</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HashCode</span><span class="p">();</span> <span class="n">hash</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">fieldA</span><span class="p">);</span> <span class="n">hash</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">fieldB</span><span class="p">);</span> <span class="n">hash</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">fieldC</span><span class="p">,</span> <span class="n">StringComparer</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span> <span class="k">return</span> <span class="n">hash</span><span class="p">.</span><span class="nf">ToHashCode</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <p>What if we had this syntax:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In theory, if AppendLiteral is an inlined no-op JIT should optimize away the call</span> <span class="c1">// This would drop any literal segments, such as whitespace, without perf penalty.</span> <span class="c1">// Just a theory, I haven't tested it.</span> <span class="c1">// Also, note the use of "i" as a format string, which in this case indicates case-insensitive comparison.</span> <span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">GetHashCode</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">HashCodeInterpolated</span><span class="p">.</span><span class="nf">Calculate</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">fieldA</span><span class="p">}</span><span class="s"> </span><span class="p">{</span><span class="n">fieldB</span><span class="p">}</span><span class="s"> </span><span class="p">{</span><span class="n">fieldC</span><span class="p">:</span><span class="n">i</span><span class="p">}</span><span class="s">"</span><span class="p">);</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>In conclusion, I think the .NET community, especially library developers, should embrace the idea that interpolated string handlers can be valuable for much more than just boosting string building performance. They open up a wide range of exciting new possibilities.</p>Brant BurnettThis blog is one of The December 17th entries on the 2021 C# Advent Calendar. Thanks for having me again Matt!C# 9 Records and Init Only Settings Without .NET 52020-12-11T11:00:00+00:002020-12-11T11:00:00+00:00https://btburnett.com/csharp/2020/12/11/initonlyproperties<p class="notice--info">This blog is one of The December 11th entries on the <a href="https://www.csadvent.christmas/">2020 C# Advent Calendar</a>. Thanks for having me again Matt!</p> <p>Some of the biggest new features in <a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9">C# 9</a> have got to be <a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types">Records</a> and <a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#init-only-setters">Init only setters</a>. But when can you safely use these features? Officially, you can only use them if you are targeting <a href="https://docs.microsoft.com/en-us/dotnet/core/dotnet-five">.NET 5</a>. However, many developers may not be ready to move to .NET 5 yet, especially since it isn’t an LTS release. And what about NuGet packages targeting .NET Standard or other versions of .NET?</p> <h2 id="tldr">TL;DR</h2> <p>For internal projects and/or types, you can safely use records and init only setters to target any modern version of .NET. I’ve tested .NET 4.8, .NET Core 2.1, and .NET Core 3.1. However, for any public members exposed via NuGet packages there are <strong>lots</strong> of caveats.</p> <p>Note: Most of the other C# 9 features are fine to use, regardless of target frameworks or consumer C# versions:</p> <ul> <li>Top-level statements</li> <li>Switch expression pattern matching enhancements</li> <li>Native sized integers (syntactic sugar for IntPtr/UIntPtr)</li> <li>Target-typed new expression</li> <li>Static anonymous functions</li> <li>GetEnumerator extension methods</li> <li>Lambda discard patterns</li> <li>Attributes on local functions</li> <li>New features for partial methods</li> </ul> <h2 id="what-are-records">What are Records?</h2> <p>There are plenty of <a href="https://anthonygiretti.com/2020/06/17/introducing-c-9-records/">blog posts</a> out there that describe records and what they do, so I won’t dig in too deep here. In simple terms, they provide a lot of boilerplate code that allows a developer to create immutable reference types with value equality, easy methods to clone the types with slightly different values, and more.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">namespace</span> <span class="nn">MyProgram</span> <span class="p">{</span> <span class="k">public</span> <span class="n">record</span> <span class="n">Person</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">LastName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="n">record</span> <span class="n">PersonWithHeight</span> <span class="p">:</span> <span class="n">Person</span> <span class="p">{</span> <span class="k">public</span> <span class="kt">int</span> <span class="n">HeightInInches</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">PersonWithHeight</span> <span class="nf">Grow</span><span class="p">(</span><span class="kt">int</span> <span class="n">inches</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="k">this</span> <span class="n">with</span> <span class="p">{</span> <span class="n">HeightInInches</span> <span class="p">=</span> <span class="n">HeightInInches</span> <span class="p">+</span> <span class="n">inches</span> <span class="p">};</span> <span class="p">}</span> <span class="p">}</span> <span class="n">PersonWithHeight</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span> <span class="p">{</span> <span class="n">FirstName</span> <span class="p">=</span> <span class="s">"Brant"</span><span class="p">,</span> <span class="n">LastName</span> <span class="p">=</span> <span class="s">"Burnett"</span><span class="p">,</span> <span class="n">HeightInInches</span> <span class="p">=</span> <span class="m">69</span> <span class="p">};</span> </code></pre></div></div> <p>Under the covers, records are really classes with a lot of boilerplate code already added to deal with equality comparisons, cloning, ToString, etc.</p> <h2 id="what-are-init-only-setters">What are init only setters?</h2> <p>Init only setters are not really specific to records, though they are quite powerful when combined. You can see an example of init only setters above in the example for records. Instead of a <code class="language-plaintext highlighter-rouge">set</code> keyword, the property uses an <code class="language-plaintext highlighter-rouge">init</code> keyword. These properties can only be set as part of an object initializer combined with the constructor, after which they may not be modified.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span> <span class="p">{</span> <span class="n">FirstName</span> <span class="p">=</span> <span class="s">"Brant"</span> <span class="c1">// Allowed</span> <span class="p">};</span> <span class="n">person</span><span class="p">.</span><span class="n">LastName</span> <span class="p">=</span> <span class="s">"Burnett"</span><span class="p">;</span> <span class="c1">// This will cause a compiler error</span> </code></pre></div></div> <p>Under the covers, C# does this by making the setter method have a slightly different signature. Normally, the signature would be <code class="language-plaintext highlighter-rouge">void set_FirstName(string value)</code>, but an init only setter has the signature <code class="language-plaintext highlighter-rouge">void modreq(System.Runtime.CompilerServices.IsExternalInit) set_FirstName(string value)</code>. Note that <code class="language-plaintext highlighter-rouge">modreq</code> can’t be directly added to a method signature in C#, this is an IL construct. C# adds it for you in this case.</p> <h2 id="requirements-to-create-records-and-init-only-setters">Requirements to Create Records and Init Only Setters</h2> <h3 id="langversion-9">LangVersion 9</h3> <p>Your project must be using C# 9 or later. If you’re targeting .NET 5, this should be the case already. However, if you target other runtimes, you may need to manually enable C# 9 in your <code class="language-plaintext highlighter-rouge">.csproj</code> file.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;LangVersion&gt;</span>9<span class="nt">&lt;/LangVersion&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> </code></pre></div></div> <p>You must also be using Visual Studio 2019 16.8 or later, or MSBuild 2019 16.8 or later, or the .NET Core SDK 5.0.100 or later. These are the versions that include the C# 9 compiler.</p> <h3 id="creating-init-only-properties-on-older-frameworks">Creating Init Only Properties on Older Frameworks</h3> <p>The key to init-only properties is the <code class="language-plaintext highlighter-rouge">IsExternalInit</code> class, which is basically nothing but a placeholder (somewhat like an empty attribute) that is applied to the <code class="language-plaintext highlighter-rouge">void</code> return type of the setter. This type is defined as part of .NET 5, but if you’re not targeting .NET 5 then it’s not available for the compiler to reference.</p> <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CS0518 Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported </code></pre></div></div> <p>To fix this, you must include the type yourself in your code. I recommend a copy/paste of the following into a file in your project. It includes conditional compilation directives which work if you’re multi-targeting so that it isn’t included unless necessary.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Licensed to the .NET Foundation under one or more agreements.</span> <span class="c1">// The .NET Foundation licenses this file to you under the MIT license.</span> <span class="c1">// See the LICENSE file in the project root for more information.</span> <span class="cp">#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 </span> <span class="k">using</span> <span class="nn">System.ComponentModel</span><span class="p">;</span> <span class="c1">// ReSharper disable once CheckNamespace</span> <span class="k">namespace</span> <span class="nn">System.Runtime.CompilerServices</span> <span class="p">{</span> <span class="c1">/// &lt;summary&gt;</span> <span class="c1">/// Reserved to be used by the compiler for tracking metadata.</span> <span class="c1">/// This class should not be used by developers in source code.</span> <span class="c1">/// &lt;/summary&gt;</span> <span class="p">[</span><span class="nf">EditorBrowsable</span><span class="p">(</span><span class="n">EditorBrowsableState</span><span class="p">.</span><span class="n">Never</span><span class="p">)]</span> <span class="k">internal</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">IsExternalInit</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> <span class="cp">#endif </span></code></pre></div></div> <h3 id="creating-records-on-older-frameworks">Creating Records on Older Frameworks</h3> <p>Records actually work fine on older frameworks without any special changes, so long as you aren’t using init only setters.</p> <p>However, records do transparently apply init only setters if you use this syntax to define the properties:</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="nf">Person</span><span class="p">(</span><span class="kt">string</span> <span class="n">FirstName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">LastName</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> </code></pre></div></div> <p>In that case, be sure to add the <code class="language-plaintext highlighter-rouge">IsExternalInit</code> class above.</p> <h2 id="consuming-records-and-init-only-properties">Consuming Records and Init Only Properties</h2> <p>What about including records and init only properties in things like NuGet packages? What are the requirements for package consumers to be able to use these features?</p> <h3 id="private-members">Private Members</h3> <p>It is safe to include <code class="language-plaintext highlighter-rouge">private</code> or <code class="language-plaintext highlighter-rouge">internal</code> records or properties with init only setters in public facing assemblies. Records are basically just syntactic sugar on top of classes, and <code class="language-plaintext highlighter-rouge">modreq</code> used by init only setters have been around a long time.</p> <h3 id="public-init-only-setters">Public Init Only Setters</h3> <p>To use a <code class="language-plaintext highlighter-rouge">public</code> or <code class="language-plaintext highlighter-rouge">protected</code> property with an init only setter, the consumer <strong>must</strong> be using a C# compiler that supports C# 9. Even if they aren’t using LangVersion 9 in their csproj, the compiler must be capable of understanding the <code class="language-plaintext highlighter-rouge">modreq</code>. If the consumer doesn’t understand the init only setter, it will be unable to access the setter at all and will appear as a read-only property.</p> <p>Similarly, other languages like VB.NET don’t like init only setters. In fact, VB.NET won’t recognize the property at all, even as read-only, and even with the latest version of the VB.NET compiler.</p> <p>As a result, I do not recommend using init only setters for public members unless it is for an internal project with a known list of consumers. All of these consumers must be C# projects, and all developers must be using the latest C# compiler.</p> <h3 id="public-records">Public Records</h3> <p>The compatibility of records across different versions of C# is a bit more confusing. First of all, if you’re using init only setters on your records (you probably are) then see those rules above.</p> <p>C# consumers can get most of the other features of records, except cloning. The clone method is hidden under the special name <code class="language-plaintext highlighter-rouge">&lt;Clone&gt;$</code> and you can’t reach it directly in C#, you must use the <code class="language-plaintext highlighter-rouge">with</code> operator which is only available in C# 9.</p> <p>The same limitation applies in VB.NET, except even on the latest version there is no equivalent of the <code class="language-plaintext highlighter-rouge">with</code> operator. This makes cloning completely unavailable.</p> <h2 id="summary">Summary</h2> <p>Okay, all of that was pretty convoluted. Variables include your target framework, your consumer’s target framework, your LangVersion, and the version of MSBuild or .NET Core SDK installed on the consumer’s development machine and build agents.</p> <p>For developers of NuGet packages which must be consumed by many users, here’s a matrix that will hopefully make things a bit clearer. These are my recommendations, some of these listed as Not Available could be considered partially available. But in my opinion the limitations aren’t worth it and it should be avoided.</p> <table> <thead> <tr> <th>Private/Internal Members</th> <th>.NET 5.0 Target</th> <th>Older Targets</th> </tr> </thead> <tbody> <tr> <td>C# .NET 5.0 Consumer</td> <td>Available</td> <td>Available (*)</td> </tr> <tr> <td>VB .NET 5.0 Consumer</td> <td>Available</td> <td>Available (*)</td> </tr> <tr> <td>C# 9 Older Framework Consumer</td> <td>n/a</td> <td>Available (*)</td> </tr> <tr> <td>C# &lt;9 Older Framework Consumer</td> <td>n/a</td> <td>Available (*)</td> </tr> <tr> <td>VB Older Framework Consumer</td> <td>n/a</td> <td>Available (*)</td> </tr> </tbody> </table> <table> <thead> <tr> <th>Public/Protected Members</th> <th>.NET 5.0 Target</th> <th>Older Targets</th> </tr> </thead> <tbody> <tr> <td>C# .NET 5.0 Consumer</td> <td>Available</td> <td>Available (*)</td> </tr> <tr> <td>VB .NET 5.0 Consumer</td> <td>Available</td> <td>Available (*)</td> </tr> <tr> <td>C# 9 Older Framework Consumer</td> <td>n/a</td> <td>Available (*)</td> </tr> <tr> <td>C# &lt;9 Older Framework Consumer</td> <td>n/a</td> <td>Not Available</td> </tr> <tr> <td>VB Older Framework Consumer</td> <td>n/a</td> <td>Not Available</td> </tr> </tbody> </table> <p>(*) = Must add IsExternalInit class snippet above</p> <h2 id="conclusion">Conclusion</h2> <p>To boil it all down even more simply, here are my overall recommendations for when you should and should not use records and init only setters.</p> <table> <thead> <tr> <th>Project Type</th> <th>Public Records</th> <th>Internal/Private Records</th> <th>Public/Protected Init Only Setters</th> <th>Internal/Private Init Only Setters</th> </tr> </thead> <tbody> <tr> <td>Internal Use</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>NuGet</td> <td>No</td> <td>Yes</td> <td>No</td> <td>Yes</td> </tr> <tr> <td>NuGet .NET 5 Target Only</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> </tbody> </table>Brant BurnettThis blog is one of The December 11th entries on the 2020 C# Advent Calendar. Thanks for having me again Matt!Reclaiming HD Space from Docker Desktop on WSL 22020-12-11T11:00:00+00:002020-12-11T11:00:00+00:00https://btburnett.com/docker/2021/09/06/docker-desktop-hd-space<p>Over time, Docker Desktop running on <a href="https://docs.docker.com/desktop/windows/wsl/">Windows Subsystem for Linux 2 (a.k.a. WSL 2)</a> may start to eat away at your hard drive space. Here is how I was able to free up over 100GB.</p> <h2 id="freeing-up-space-within-docker">Freeing up space within Docker</h2> <p>The first problem is freeing up the space being used by Docker internally. This may include container images, containers, dangling image layers, volumes, and even networks. There are several different command which will help clean these up.</p> <p>It isn’t necessarily required to perform all of these steps, you should edit based on your particular needs. I will say that container images are the biggest culprit in most cases. Also, in general it is safe to delete container images, as they may be downloaded again as needed.</p> <p>Since we’re talking primarily about Windows, these examples are in Powershell. However, the general concept should work from Bash as well.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Delete all containers (if acceptable). This allows the images they are referencing to be deleted,</span><span class="w"> </span><span class="c"># but is not required. The first command ensures that all containers are stopped.</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">ps</span><span class="w"> </span><span class="nt">-q</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">stop</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">ps</span><span class="w"> </span><span class="nt">-aq</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">rm</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c"># Delete all container images. This may return a few errors but that is usually fine.</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">images</span><span class="w"> </span><span class="nt">--format</span><span class="w"> </span><span class="s2">"{{.Repository}}:{{.Tag}}"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">rmi</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>Once this is done, further commands will do more cleanup:</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cleans up several different items.</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">system</span><span class="w"> </span><span class="nx">prune</span><span class="w"> </span><span class="c"># Optionally, this format will also delete your Docker storage volumes. Beware of data loss.</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">system</span><span class="w"> </span><span class="nx">prune</span><span class="w"> </span><span class="nt">--volumes</span><span class="w"> </span></code></pre></div></div> <p>If you want to delete some volumes, but leave others in place, I have used scripts like this one in the past.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Delete all volumes with names that do not contain "halyard" or "vscode".</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">volume</span><span class="w"> </span><span class="nx">ls</span><span class="w"> </span><span class="nt">-q</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">where</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="bp">$_</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"halyard"</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"vscode"</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">volume</span><span class="w"> </span><span class="nx">rm</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h2 id="reclaiming-the-space-for-windows">Reclaiming the space for Windows</h2> <p>After freeing space within Docker, the space will (unfortunately) not be freed for Windows to use. The problem is that Docker keeps its data within a VHDX virtual drive file. This file now has lots of free space within it, but it needs to be shrunk.</p> <p>Adding to the problem is that Windows Hyper-V isn’t great about recognizing unused sectors within a VHDX file using the EXT4 Linux file system. I won’t pretend to be an expert, but my understanding is that we need to convince Linux to send TRIM commands to the hardware abstraction layer to indicate unused sectors. Once this is done, Hyper-V can more successfully shrink the VHDX file.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Run this as an Administrator, not all of these steps work as a regular Windows user.</span><span class="w"> </span><span class="c"># Perform the TRIM commands and then delete the image we just used.</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nt">--rm</span><span class="w"> </span><span class="nt">--privileged</span><span class="w"> </span><span class="nt">--pid</span><span class="o">=</span><span class="nf">host</span><span class="w"> </span><span class="nx">docker/desktop-reclaim-space</span><span class="w"> </span><span class="nf">docker</span><span class="w"> </span><span class="nx">rmi</span><span class="w"> </span><span class="nx">docker/desktop-reclaim-space</span><span class="w"> </span><span class="c"># Shutdown Docker and WSL 2. You may wish to exit Docker Desktop gracefully before this step.</span><span class="w"> </span><span class="c"># If you get file in use errors on the next step, not performing this step is the likely culprit.</span><span class="w"> </span><span class="nf">wsl</span><span class="w"> </span><span class="nt">--shutdown</span><span class="w"> </span><span class="c"># Optimize the VHDX file to reclaim free space. This may take a few minutes.</span><span class="w"> </span><span class="nf">Optimize-VHD</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">${env:LOCALAPPDATA}</span><span class="nx">\Docker\wsl\data\ext4.vhdx</span><span class="w"> </span><span class="nt">-Mode</span><span class="w"> </span><span class="nx">Full</span><span class="w"> </span></code></pre></div></div> <h3 id="side-note">Side note</h3> <p>If you’re running antivirus, be sure you’ve excluded VHDX files from real time scans for better performance within Docker. See <a href="https://docs.microsoft.com/en-us/troubleshoot/windows-server/virtualization/antivirus-exclusions-for-hyper-v-hosts">Recommended antivirus exclusions for Hyper-V hosts</a> for details.</p> <h2 id="conclusion">Conclusion</h2> <p>In my case, this took my VHDX file from over 120 GB to about 10GB, which was a pretty big win for my laptop’s 500GB SSD. Please let me know if you find any other tips/tricks in the comments that I should add to this post.</p>Brant BurnettOver time, Docker Desktop running on Windows Subsystem for Linux 2 (a.k.a. WSL 2) may start to eat away at your hard drive space. Here is how I was able to free up over 100GB.IAsyncEnumerable Is Your Friend, Even In .NET Core 2.x2019-12-01T11:00:00+00:002019-12-01T11:00:00+00:00https://btburnett.com/csharp/2019/12/01/asyncenumberable<p class="notice--info">This blog is one of The December 1st entries on the <a href="https://crosscuttingconcerns.com/The-Third-Annual-csharp-Advent">2019 C# Advent Calendar</a>. Thanks for having me again Matt!</p> <p>My favorite new feature in <a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8">C# 8</a> has got to be <a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#asynchronous-streams">Asynchronous Streams</a>, a.k.a. Asynchronous Enumerables. However, I think there may some confusion as to what they do, when to use them, and even if they can be used in a particular project. This post will hopefully clarify some of these points.</p> <h2 id="tldr">TL;DR</h2> <p>You can use <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> and the related C# 8 features in .NET Core 2.x or .NET Framework 4.6.1, not just .NET Core 3.0!</p> <h2 id="why-use-iasyncenumerablet">Why use <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code>?`</h2> <p>When writing efficient applications intended to handle large numbers of simultaneous operations, such as web applications, blocking operations are the enemy. Any time an application is waiting on some kind of I/O-bound operation, such as a network response or a hard disk read, it is always best to relinquish the thread back to the thread pool. This allows the CPU to work on other operations while the method is waiting, and then continue the work once there is more to be done, without using up all of the threads in the thread pool. Lots of details about why are <a href="https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming">available here</a>.</p> <p>In many cases, it’s efficient enough to simply return a regular <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;</code> asynchronously, like so:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DoWork</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">query</span> <span class="p">=</span> <span class="nf">BuildMyQuery</span><span class="p">();</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">XYZ</span><span class="p">&gt;</span> <span class="n">queryResult</span> <span class="p">=</span> <span class="k">await</span> <span class="n">query</span><span class="p">.</span><span class="nf">ExecuteAsync</span><span class="p">();</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">queryResult</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Do work here</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>However, this is may not be the most efficient approach. There are two potential limitations, depending on the backing implementation of <code class="language-plaintext highlighter-rouge">ExecuteAsync</code> in the example above.</p> <ol> <li>The implementation of ExecuteAsync may return after it gets the first part of the data, or even none. If it is enumerated faster than the data is arriving it will block waiting for more items to arrive. This will block the executing thread.</li> <li>The implementation of ExecuteAsync may wait until it has <strong>all</strong> of the data before returning. This delays the point where DoWork may begin processing data.</li> </ol> <p><code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> and its sibling <code class="language-plaintext highlighter-rouge">IAsyncEnumerator&lt;T&gt;</code>, on the other hand, return a <code class="language-plaintext highlighter-rouge">Task</code> as the stream is iterated. This allows methods such as <code class="language-plaintext highlighter-rouge">ExecuteAsync</code> to return early and then wait for more data as it is iterated, without blocking the thread.</p> <h2 id="when-not-to-use-iasyncenumerable">When <strong>Not</strong> To Use IAsyncEnumerable</h2> <p>Don’t use <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> for any collection which is inherently synchronous and CPU-bound. For those cases, continuing using <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;</code>. The extra overhead of handling asynchronous tasks will usually be less performant in these scenarios.</p> <p>Knowing which type to use as the return type on an interface method is a bit tricker. The interface can have different backing implementations which may or may not be synchronous. In this case, use the pattern for the most likely scenario. If in doubt, lean towards <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> because it can be used for either scenario and is therefore more flexible.</p> <h2 id="returning-an-iasyncenumerable-in-c-8">Returning an IAsyncEnumerable in C# 8</h2> <p>For simple use cases, returning an <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> using an iterator function is as easy as an <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;</code>.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">IAsyncEnumerable</span><span class="p">&lt;</span><span class="n">XYZ</span><span class="p">&gt;</span> <span class="nf">GetXYZAsync</span><span class="p">()</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span><span class="p">=</span><span class="m">0</span><span class="p">;</span> <span class="n">i</span><span class="p">&lt;</span><span class="m">20</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">item</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetXYZById</span><span class="p">(</span><span class="n">i</span><span class="p">);</span> <span class="k">yield</span> <span class="k">return</span> <span class="n">item</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>It is also possible to write an iterator method which returns <code class="language-plaintext highlighter-rouge">IAsyncEnumerator&lt;T&gt;</code>.</p> <p>If the method supports cancellation, the CancellationToken should to be decorated with the <code class="language-plaintext highlighter-rouge">EnumeratorCancellation</code> attribute:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">IAsyncEnumerable</span><span class="p">&lt;</span><span class="n">XYZ</span><span class="p">&gt;</span> <span class="nf">GetXYZAsync</span><span class="p">([</span><span class="n">EnumeratorCancellation</span><span class="p">]</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">default</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span><span class="p">=</span><span class="m">0</span><span class="p">;</span> <span class="n">i</span><span class="p">&lt;</span><span class="m">20</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">item</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetXYZById</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span> <span class="k">yield</span> <span class="k">return</span> <span class="n">item</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="consuming-an-iasyncenumerable-in-c-8">Consuming an IAsyncEnumerable in C# 8</h2> <p>To consume an IAsyncEnumerable, simply use the new <code class="language-plaintext highlighter-rouge">await foreach</code> statement within an asynchronous method.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="nf">GetXYZAsync</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// do things here</span> <span class="p">}</span> </code></pre></div></div> <p>To control the synchronization context, <code class="language-plaintext highlighter-rouge">ConfigureAwait</code> is available, just like on <code class="language-plaintext highlighter-rouge">Task</code>.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="nf">GetXYZAsync</span><span class="p">().</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// do things here</span> <span class="p">}</span> </code></pre></div></div> <p>To pass a cancellation token:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">await</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">item</span> <span class="k">in</span> <span class="nf">GetXYZAsync</span><span class="p">().</span><span class="nf">WithCancellation</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// do things here</span> <span class="p">}</span> </code></pre></div></div> <h2 id="special-compatibility-concerns">Special Compatibility Concerns</h2> <p>Depending on the type of project and the version of .NET being targeted, there may be concerns about compatibility. One myth is that these features can only be used with .NET Core 3.0 and C# 8.</p> <h3 id="can-i-use-iasyncenumerable-when-targeting-net-core-2x">Can I Use IAsyncEnumerable When Targeting .NET Core 2.x?</h3> <p><strong>Short answer</strong>: Yes</p> <p>To gain access to IAsyncEnumerable, install the compatibility NuGet package <a href="https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/">Microsoft.Bcl.AsyncInterfaces</a>. This provides the types that are missing in .NET Standard 2.0.</p> <p>However, producing and consuming IAsyncEnumerables is a bit more difficult. Here’s an example consumer:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">enumerable</span> <span class="p">=</span> <span class="nf">GetXYZAsync</span><span class="p">();</span> <span class="kt">var</span> <span class="n">enumerator</span> <span class="p">=</span> <span class="n">enumerable</span><span class="p">.</span><span class="nf">GetAsyncEnumerator</span><span class="p">();</span> <span class="k">try</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span><span class="k">await</span> <span class="n">enumerator</span><span class="p">.</span><span class="nf">MoveNextAsync</span><span class="p">())</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">item</span> <span class="p">=</span> <span class="n">enumerator</span><span class="p">.</span><span class="n">Current</span><span class="p">;</span> <span class="c1">// Do things here</span> <span class="p">}</span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="k">await</span> <span class="n">enumerator</span><span class="p">.</span><span class="nf">DisposeAsync</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <p>To get around this limitation, there are ways to access C# 8 language features, like asynchronous streams, even from .NET Core 2.x. The requirements are:</p> <ul> <li>Use .NET Core SDK 3.0 or MSBuild Tools 2019 as the compiler.</li> <li>Use Visual Studio 2019 or VSCode as the IDE.</li> <li>Add a <code class="language-plaintext highlighter-rouge">&lt;LangVersion&gt;8&lt;/LangVersion&gt;</code> property to the project file.</li> </ul> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span> <span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;OutputType&gt;</span>Exe<span class="nt">&lt;/OutputType&gt;</span> <span class="nt">&lt;TargetFramework&gt;</span>netcoreapp2.0<span class="nt">&lt;/TargetFramework&gt;</span> <span class="nt">&lt;LangVersion&gt;</span>8<span class="nt">&lt;/LangVersion&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> <span class="nt">&lt;ItemGroup&gt;</span> <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.Bcl.AsyncInterfaces"</span> <span class="na">Version=</span><span class="s">"1.0.0"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/ItemGroup&gt;</span> <span class="nt">&lt;/Project&gt;</span> </code></pre></div></div> <h3 id="can-i-use-iasyncenumerable-when-targeting-net-framework">Can I Use IAsyncEnumerable When Targeting .NET Framework</h3> <p><strong>Short answer</strong>: Yes, 4.6.1 or later</p> <p>.NET Framework 4.6.1 is compatible with .NET Standard 2.0. Therefore, so long as the project is targeting .NET Framework 4.6.1 or later, the same rules apply as for .NET Core 2.x. Just follow the same steps as above.</p> <h3 id="what-about-nuget-packages">What About NuGet Packages?</h3> <p><strong>Short answer</strong>: Yes, targeting .NET Standard 2.0 or later</p> <p>When creating a NuGet package, maintaining backwards compatibility is key. This can make it difficult to use new features like asynchronous streams. The good news is that there is a relatively easy path.</p> <ul> <li>Dual target .NET Standard 2.0 and 2.1.</li> <li>Consider targeting .NET 4.6.1 as well, see <a href="https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/cross-platform-targeting">Microsoft’s guidance on cross platform targeting</a>.</li> <li>Include <a href="https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/">Microsoft.Bcl.AsyncInterfaces</a> for the lower version targets only.</li> <li>Add a <code class="language-plaintext highlighter-rouge">&lt;LangVersion&gt;8&lt;/LangVersion&gt;</code> property to the project file.</li> </ul> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span> <span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;TargetFrameworks&gt;</span>net461;netstandard2.0;netstandard2.1<span class="nt">&lt;/TargetFrameworks&gt;</span> <span class="nt">&lt;LangVersion&gt;</span>8<span class="nt">&lt;/LangVersion&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> <span class="nt">&lt;ItemGroup</span> <span class="na">Condition=</span><span class="s">" '$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'netstandard2.0' "</span><span class="nt">&gt;</span> <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.Bcl.AsyncInterfaces"</span> <span class="na">Version=</span><span class="s">"1.0.0"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/ItemGroup&gt;</span> <span class="nt">&lt;/Project&gt;</span> </code></pre></div></div> <p>However, this approach has problems if the package needs to target versions of .NET Core before 2.0, .NET Standard before 2.0, or .NET Framework before 4.6.1. The <code class="language-plaintext highlighter-rouge">Microsoft.Bcl.AsyncInterfaces</code> package isn’t compatible with frameworks prior these versions. This can still be addressed by using preprocessor conditionals within the codebase to exclude <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> support, but is much more cumbersome and outside the scope of this post.</p> <h2 id="what-about-linq">What about LINQ?</h2> <p>Most .NET developers love to use LINQ, either using the query syntax or using the functional syntax like <code class="language-plaintext highlighter-rouge">.Where(p =&gt; p != null)</code> or <code class="language-plaintext highlighter-rouge">.Select(p =&gt; p.Property)</code>. Is it possible to use LINQ with <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code>?</p> <p>To get support for the functional LINQ syntax, such as <code class="language-plaintext highlighter-rouge">.Where(p =&gt; p != null)</code> or <code class="language-plaintext highlighter-rouge">.Select(p =&gt; p.Property)</code>, install <a href="https://www.nuget.org/packages/System.Linq.Async/">System.Linq.Async</a>. This package is brought to us by the group that makes <a href="http://reactivex.io/">ReactiveX</a>. Be sure to use at least version 4.0.0.</p> <h2 id="conclusion">Conclusion</h2> <p>Don’t be afraid to use <code class="language-plaintext highlighter-rouge">IAsyncEnumerable&lt;T&gt;</code> in your code. It’s really a lot more available than most developers think it is, and is very easy to use.</p>Brant BurnettThis blog is one of The December 1st entries on the 2019 C# Advent Calendar. Thanks for having me again Matt!Blue/Green Message Bus Handling on Kubernetes with Shawarma2019-08-12T06:00:00+00:002019-08-12T06:00:00+00:00https://btburnett.com/kubernetes/microservices/continuous%20delivery/2019/08/12/shawarma<p>In the modern world of cloud-based microservices and continuous delivery, <a href="https://martinfowler.com/bliki/BlueGreenDeployment.html">blue/green deployments</a> (a.k.a red/black deployments) have become commonplace. This approach provides a way to safely deploy an application with zero down time, and quickly roll back to the previous version if there is a problem. If you’re deploying in the cloud and <strong>not</strong> using blue/gren deployments, stop reading now and go find out what you’re missing.</p> <p>At <a href="https://centeredgesoftware.com/">CenterEdge Software</a> our primary deployment strategy is blue/green deployments to <a href="https://kubernetes.io/">Kubernetes</a> using <a href="https://www.spinnaker.io/">Spinnaker</a>. However, in practice we quickly discovered a limitation of the typical blue/green deployment strategy.</p> <h2 id="tldr">TL;DR</h2> <p>We’ve solved the problem of blue/green deployments of message bus consumers on Kubernetes using a lightweight sidecar and a simple application plugin. We call the system <a href="https://github.com/CenterEdge/shawarma">Shawarma</a>. (Yes, it’s an Avengers MCU reference)</p> <h2 id="the-problem">The Problem</h2> <p>The key to blue/green deployments is the ability to route incoming requests to just the active version of the service, leaving the inactive version idle. This works great for incoming HTTP requests, as the blue/green deployment pipeline will put the active service on the load balancer and remove the inactive service. You’ll only have a brief window where both versions are serving HTTP requests.</p> <p>However, queued messages being processed from your message bus, such as <a href="https://kafka.apache.org/">Kafka</a> or <a href="https://www.rabbitmq.com/">RabbitMQ</a>, should be restricted as well. These also represent a form of input, like HTTP requests, which should only be processed by the active version. However, message bus processing is normally a pull process, not push, which makes the problem more complicated.</p> <h2 id="possible-solutions">Possible Solutions</h2> <p>There are several possible approaches to solving the problem, but many suffer from too much complexity. For example, one approach is to separate message bus processing from the service and forward requests to the service over HTTP. I’ll call this the Redirection Approach.</p> <p><img src="/assets/images/shawarma-redirection.jpg" alt="Redirection Approach" /></p> <p>This approach helps to maintain separation of concerns, since the service itself isn’t required to know about its deployment or understand if it’s the active or inactive version. However, it adds a lot of complexity, requires deploying an additional service, and results in a signifcant performance hit for high-volume queues.</p> <p>Another approach, which I’ll call the Colorful Service Approach, requires that the service know about its deployment status by monitoring the load balancer.</p> <p><img src="/assets/images/shawarma-colorful.jpg" alt="Colorful Service Approach" /></p> <p>The Colorful Service Approach is simpler in many ways, but now our concerns are not separate. The service itself needs to know about the deployment environment, how to check the status of the load balancer, and more. While this works, it doesn’t make for a very <a href="https://www.thoughtworks.com/insights/blog/microservices-evolutionary-architecture">Evolutionary Architecture</a>.</p> <h2 id="the-shawarma-solution">The Shawarma Solution</h2> <p>At CenterEdge, we’ve created our own, open source solution to this problem, <a href="https://github.com/CenterEdge/shawarma">Shawarma</a>. Shawarma is specifically designed for Kubernetes, and works via a modified version of the Colorful Service Approach. It simplifies the design even further, and maintains separation of concerns by keeping the application itself agnostic. It takes advatange of the Kubernetes API and built-in support for watch commands to receive notifications about state changes within the Kubernetes cluster.</p> <p>In the Shawarma Approach, each service gets a single additional HTTP endpoint which receives POST operations informing the service of its state, active or inactive. It doesn’t know anything at all about the deployment environment, it only knows this single, basic data point.</p> <p>The brains of the operation is a Shawarma sidecar deployed within the Kubernetes pod. This sidecar container knows about Kubernetes and its Services and Endpoints, and monitors Kubernetes to know if the application is active or inactive on the assigned load balancer. When the status changes, it makes an HTTP POST calls to notify the main service. The main service can then discontinue or restart any background processing, including message bus processing.</p> <p><img src="/assets/images/shawarma-main.jpg" alt="Shawarma Approach" /></p> <h2 id="shawarma-implementation">Shawarma Implementation</h2> <p>The Shawarma sidecar is implemented as a very lightweight Go application, available on <a href="https://cloud.docker.com/u/centeredge/repository/docker/centeredge/shawarma">Docker Hub</a>. At this point it’s in beta testing at CenterEdge, but the early signs are very encouraging.</p> <p>Additionally, we recognized very quickly that deployments needed to be kept as simple as possible. The need to configure dozens of microservices with a sidecar container was daunting. To that end, we’ve created a simple Kubernetes <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook">MutatingAdmissionWebhook</a>. When the Shawarma Webhook is deployed, a simple annotation on a Pod (or Deployment template) will automatically configure the sidecar.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span> <span class="na">metadata</span><span class="pi">:</span> <span class="na">labels</span><span class="pi">:</span> <span class="na">app</span><span class="pi">:</span> <span class="s">shawarma-example</span> <span class="na">active</span><span class="pi">:</span> <span class="s1">'</span><span class="s">true'</span> <span class="na">annotations</span><span class="pi">:</span> <span class="c1"># The annotation value is name of the service for Shawarma to monitor</span> <span class="s">shawarma.centeredge.io/service-name</span><span class="pi">:</span> <span class="s">shawarma-example</span> <span class="na">spec</span><span class="pi">:</span> <span class="c1"># ...</span> </code></pre></div></div> <p>A full example set <a href="https://github.com/CenterEdge/shawarma/tree/master/example/injected">can be found here</a>.</p> <h2 id="application-state-in-aspnet-core">Application State in ASP.NET Core</h2> <p>Since CenterEdge is a .NET shop, we’ve also implemented some supporting NuGet packages to handle the application state.</p> <table> <thead> <tr> <th>Package</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><a href="https://www.nuget.org/packages/Shawarma.Abstractions/">Shawarma.Abstractions</a></td> <td>Basic abstractions and models</td> </tr> <tr> <td><a href="https://www.nuget.org/packages/Shawarma.AspNetCore/">Shawarma.AspNetCore</a></td> <td>.NET Core middleware for receiving and handling application state</td> </tr> <tr> <td><a href="https://www.nuget.org/packages/Shawarma.AspNetCore.Hosting/">Shawarma.Hosting</a></td> <td>The <code class="language-plaintext highlighter-rouge">IShawarmaService</code> system, an equivalent to <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&amp;tabs=visual-studio">IHostedService</a> which starts and stops based on application state</td> </tr> </tbody> </table> <p>Using these SDK packages, adding slim, agnostic handling of Shawarma application state is easy.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span> <span class="p">{</span> <span class="n">services</span> <span class="p">.</span><span class="nf">AddMvc</span><span class="p">()</span> <span class="p">.</span><span class="nf">AddShawarmaHosting</span><span class="p">()</span> <span class="c1">// Add any IShawarmaService instances to be managed</span> <span class="p">.</span><span class="n">AddShawarmaService</span><span class="p">&lt;</span><span class="n">TestService</span><span class="p">&gt;();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Configure</span><span class="p">(</span><span class="n">IApplicationBuilder</span> <span class="n">app</span><span class="p">,</span> <span class="n">IHostingEnvironment</span> <span class="n">env</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">env</span><span class="p">.</span><span class="nf">IsDevelopment</span><span class="p">())</span> <span class="p">{</span> <span class="n">app</span><span class="p">.</span><span class="nf">UseDeveloperExceptionPage</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// Add before MVC or other handlers</span> <span class="n">app</span> <span class="p">.</span><span class="nf">UseShawarma</span><span class="p">()</span> <span class="p">.</span><span class="nf">UseMvc</span><span class="p">();</span> <span class="p">}</span> </code></pre></div></div> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">TestService</span> <span class="p">:</span> <span class="n">GenericShawarmaService</span> <span class="p">{</span> <span class="k">public</span> <span class="nf">TestService</span><span class="p">(</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">TestService</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">logger</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">protected</span> <span class="k">override</span> <span class="n">Task</span> <span class="nf">StartInternalAsync</span><span class="p">(</span><span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Start doing work here</span> <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span> <span class="p">}</span> <span class="k">protected</span> <span class="k">override</span> <span class="n">Task</span> <span class="nf">StopInternalAsync</span><span class="p">(</span><span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Stop doing work here</span> <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>Hopefully, the Shawarma project will be helpful to other services deploying microservices to Kubernetes. We’re very open to feedback, <a href="https://github.com/CenterEdge/shawarma/issues">issues may be filed at GitHub</a>. We’d also love some help from the community developing application state SDKs for other frameworks such as Java and Node.</p> <p>It’s also worth nothing that the Shawarma Approach is useful for more than just message bus processing. Any kind of background work being handled outside of HTTP requests can benefit, such as scheduled tasks or polling remote data sources.</p>Brant BurnettIn the modern world of cloud-based microservices and continuous delivery, blue/green deployments (a.k.a red/black deployments) have become commonplace. This approach provides a way to safely deploy an application with zero down time, and quickly roll back to the previous version if there is a problem. If you’re deploying in the cloud and not using blue/gren deployments, stop reading now and go find out what you’re missing.A Restful Job Pattern For A C# Microservice2018-12-06T12:30:00+00:002018-12-06T12:30:00+00:00https://btburnett.com/couchbase/microservices/rest/2018/12/06/a-restuful-job-pattern-for-a-csharp-microservice<p class="notice--info">This blog is one of The December 6th entries on the <a href="https://crosscuttingconcerns.com/The-Second-Annual-C-Advent">2018 C# Advent Calendar</a>. Thanks for having me again Matt!</p> <p>One of the key differences between REST (Representational State Transfer) and RPC (Remote Procedure Calls) is that all actions within REST are conducted as if getting or mutating a specific resource. In most cases, especially basic CRUDL operations, this makes for a cleaner and more concise API structure than RPC. However, it can also add friction in certain cases.</p> <p>One such case is dealing with long-running jobs. What if creating a new resource takes time and isn’t immediately available? I won’t go into the details in this post, <a href="https://farazdagi.com/2014/rest-and-long-running-jobs/">here is a post by Victor Farazdagi that provides an overview</a>. However, I’m going to use the basic endpoints he discusses in my examples.</p> <p>This concept is great, but what about the practical implementation? If your application is a based on a single, long-lived server that never fails or reboots, read no further because the implementation is easy. If, on the other hand, your application operates in the real world of ephemeral servers, load balancers, microservices, and unreliable hardware, keep reading!</p> <p class="notice--info"><strong>Note:</strong> The completed and runnable example is available at <a href="https://github.com/brantburnett/CouchbaseRestfulJobPattern">https://github.com/brantburnett/CouchbaseRestfulJobPattern</a>.</p> <h2 id="the-architecture">The Architecture</h2> <p>Before I dig into an implementation, let’s discuss the high-level architecture of the application.</p> <ul> <li>The application is a microservice running in a Docker container</li> <li>There will be multiple instances of the application behind a load balancer</li> <li>Any given instance may die at any given time due to hardware failure, scale-in, etc.</li> <li>The instances all share a data store (in this case <a href="https://www.couchbase.com/">Couchbase</a>, but any data store supporting atomic writes would suffice)</li> <li>The instances are connected by a message bus which supports At Least Once delivery (such as <a href="https://www.rabbitmq.com/">RabbitMQ</a>, <a href="https://kafka.apache.org/">Kafka</a>, etc)</li> </ul> <p>I’m also going to assume that we already have the basic application in place with endpoints for managing stars, but it currently doesn’t use the job pattern.</p> <h2 id="the-requirements">The Requirements</h2> <ol> <li>When a star is created, we should return quickly with a link to a long-running job</li> <li>There should be a way to monitor the job for completion and retrieve to star ID</li> <li>Jobs should trigger quickly under normal circumstances</li> <li>Job processing load should be at least somewhat balanced across application instances</li> <li>Jobs should only run on one application instance at a time</li> <li>An application instance shouldn’t oversubscribe to too many jobs at once</li> <li>Job processing should not prevent the application from exiting during scale-in or maintenance</li> <li>Jobs should be resumed on another application instance if an instance is stopped or fails</li> </ol> <h2 id="the-job-document">The Job Document</h2> <p>First, let’s create a <code class="language-plaintext highlighter-rouge">JobRepository</code> that can be used to persist the job and its status to the database. We’ll support document expirations on jobs so that old, completed jobs can be cleaned up eventually.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">DocumentTypeFilter</span><span class="p">(</span><span class="n">TypeString</span><span class="p">)]</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">Job</span> <span class="p">{</span> <span class="k">private</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">TypeString</span> <span class="p">=</span> <span class="s">"job"</span><span class="p">;</span> <span class="k">public</span> <span class="kt">long</span> <span class="n">Id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Type</span> <span class="p">=&gt;</span> <span class="n">TypeString</span><span class="p">;</span> <span class="p">[</span><span class="nf">JsonProperty</span><span class="p">(</span><span class="n">NullValueHandling</span> <span class="p">=</span> <span class="n">NullValueHandling</span><span class="p">.</span><span class="n">Ignore</span><span class="p">)]</span> <span class="k">public</span> <span class="n">Star</span> <span class="n">CreateStar</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">[</span><span class="nf">JsonConverter</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">StringEnumConverter</span><span class="p">))]</span> <span class="k">public</span> <span class="n">JobStatus</span> <span class="n">Status</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">GetKey</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="s">$"</span><span class="p">{</span><span class="n">TypeString</span><span class="p">}</span><span class="s">-</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">JobRepository</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">IBucket</span> <span class="n">_bucket</span><span class="p">;</span> <span class="k">public</span> <span class="nf">JobRepository</span><span class="p">(</span><span class="n">IDefaultBucketProvider</span> <span class="n">bucketProvider</span><span class="p">)</span> <span class="p">{</span> <span class="n">_bucket</span> <span class="p">=</span> <span class="n">bucketProvider</span><span class="p">.</span><span class="nf">GetBucket</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;&gt;</span> <span class="nf">GetAllJobsAsync</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">context</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BucketContext</span><span class="p">(</span><span class="n">_bucket</span><span class="p">);</span> <span class="k">return</span> <span class="n">context</span><span class="p">.</span><span class="n">Query</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Id</span><span class="p">)</span> <span class="p">.</span><span class="nf">ExecuteAsync</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;&gt;</span> <span class="nf">GetIncompleteJobsAsync</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">context</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BucketContext</span><span class="p">(</span><span class="n">_bucket</span><span class="p">);</span> <span class="k">return</span> <span class="n">context</span><span class="p">.</span><span class="n">Query</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;()</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Status</span> <span class="p">!=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Complete</span><span class="p">)</span> <span class="p">.</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Id</span><span class="p">)</span> <span class="p">.</span><span class="nf">ExecuteAsync</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;</span> <span class="nf">GetJobAsync</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_bucket</span><span class="p">.</span><span class="n">GetDocumentAsync</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;(</span><span class="n">Job</span><span class="p">.</span><span class="nf">GetKey</span><span class="p">(</span><span class="n">id</span><span class="p">));</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">Status</span> <span class="p">==</span> <span class="n">ResponseStatus</span><span class="p">.</span><span class="n">KeyNotFound</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">null</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Throw an exception on a low-level error</span> <span class="n">result</span><span class="p">.</span><span class="nf">EnsureSuccess</span><span class="p">();</span> <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">Content</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">CreateJobAsync</span><span class="p">(</span><span class="n">Job</span> <span class="n">job</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">job</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">job</span><span class="p">));</span> <span class="p">}</span> <span class="n">job</span><span class="p">.</span><span class="n">Id</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetNextJobIdAsync</span><span class="p">();</span> <span class="kt">var</span> <span class="n">document</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Document</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Job</span><span class="p">.</span><span class="nf">GetKey</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">),</span> <span class="n">Content</span> <span class="p">=</span> <span class="n">job</span> <span class="p">};</span> <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_bucket</span><span class="p">.</span><span class="nf">InsertAsync</span><span class="p">(</span><span class="n">document</span><span class="p">);</span> <span class="n">result</span><span class="p">.</span><span class="nf">EnsureSuccess</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">UpdateJobAsync</span><span class="p">(</span><span class="n">Job</span> <span class="n">job</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">?</span> <span class="n">expiration</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">job</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">job</span><span class="p">));</span> <span class="p">}</span> <span class="kt">var</span> <span class="n">document</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Document</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;</span> <span class="p">{</span> <span class="n">Id</span> <span class="p">=</span> <span class="n">Job</span><span class="p">.</span><span class="nf">GetKey</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">),</span> <span class="n">Expiry</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span> <span class="p">(</span><span class="n">expiration</span><span class="p">?.</span><span class="n">TotalMilliseconds</span> <span class="p">??</span> <span class="m">0</span><span class="p">),</span> <span class="n">Content</span> <span class="p">=</span> <span class="n">job</span> <span class="p">};</span> <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_bucket</span><span class="p">.</span><span class="nf">ReplaceAsync</span><span class="p">(</span><span class="n">document</span><span class="p">);</span> <span class="n">result</span><span class="p">.</span><span class="nf">EnsureSuccess</span><span class="p">();</span> <span class="p">}</span> <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">long</span><span class="p">&gt;</span> <span class="nf">GetNextJobIdAsync</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// Redacted here for clarity</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Next, let’s create <code class="language-plaintext highlighter-rouge">JobService</code> to handle business logic associated with these jobs (we’ll be fleshing this out more later).</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">JobService</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">JobRepository</span> <span class="n">_jobRepository</span><span class="p">;</span> <span class="k">public</span> <span class="nf">JobService</span><span class="p">(</span><span class="n">JobRepository</span> <span class="n">jobRepository</span><span class="p">)</span> <span class="p">{</span> <span class="n">_jobRepository</span> <span class="p">=</span> <span class="n">jobRepository</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">jobRepository</span><span class="p">));</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;</span> <span class="nf">CreateStarJobAsync</span><span class="p">(</span><span class="n">Star</span> <span class="n">star</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">job</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Job</span> <span class="p">{</span> <span class="n">CreateStar</span> <span class="p">=</span> <span class="n">star</span><span class="p">,</span> <span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Queued</span> <span class="p">};</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">CreateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">);</span> <span class="k">return</span> <span class="n">job</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Next, we need a simple controller to get Job status.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"job"</span><span class="p">)]</span> <span class="p">[</span><span class="n">ApiController</span><span class="p">]</span> <span class="k">public</span> <span class="k">class</span> <span class="nc">JobsController</span> <span class="p">:</span> <span class="n">ControllerBase</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">JobRepository</span> <span class="n">_jobRepository</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">IMapper</span> <span class="n">_mapper</span><span class="p">;</span> <span class="k">public</span> <span class="nf">JobsController</span><span class="p">(</span><span class="n">JobRepository</span> <span class="n">jobRepository</span><span class="p">,</span> <span class="n">IMapper</span> <span class="n">mapper</span><span class="p">)</span> <span class="p">{</span> <span class="n">_jobRepository</span> <span class="p">=</span> <span class="n">jobRepository</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">jobRepository</span><span class="p">));</span> <span class="n">_mapper</span> <span class="p">=</span> <span class="n">mapper</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">mapper</span><span class="p">));</span> <span class="p">}</span> <span class="c1">// GET job</span> <span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ActionResult</span><span class="p">&lt;</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">JobDto</span><span class="p">&gt;&gt;&gt;</span> <span class="nf">Get</span><span class="p">()</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">jobs</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">GetAllJobsAsync</span><span class="p">();</span> <span class="k">return</span> <span class="n">jobs</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">_mapper</span><span class="p">.</span><span class="n">Map</span><span class="p">&lt;</span><span class="n">JobDto</span><span class="p">&gt;(</span><span class="n">p</span><span class="p">)).</span><span class="nf">ToList</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// GET job/5</span> <span class="p">[</span><span class="nf">HttpGet</span><span class="p">(</span><span class="s">"{id}"</span><span class="p">)]</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ActionResult</span><span class="p">&lt;</span><span class="n">JobDto</span><span class="p">&gt;&gt;</span> <span class="nf">Get</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">GetJobAsync</span><span class="p">(</span><span class="n">id</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">NotFound</span><span class="p">();</span> <span class="p">}</span> <span class="k">return</span> <span class="n">_mapper</span><span class="p">.</span><span class="n">Map</span><span class="p">&lt;</span><span class="n">JobDto</span><span class="p">&gt;(</span><span class="n">result</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And, finally, we need to modify <code class="language-plaintext highlighter-rouge">StarsController</code> to create the job when a request is made, and return 202 Accepted as the status with a Location header.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">HttpPost</span><span class="p">]</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">Post</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="n">StarDto</span> <span class="n">star</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">ModelState</span><span class="p">.</span><span class="n">IsValid</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">BadRequest</span><span class="p">();</span> <span class="p">}</span> <span class="n">star</span><span class="p">.</span><span class="n">Id</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="kt">var</span> <span class="n">job</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobService</span><span class="p">.</span><span class="nf">CreateStarJobAsync</span><span class="p">(</span><span class="n">_mapper</span><span class="p">.</span><span class="n">Map</span><span class="p">&lt;</span><span class="n">Star</span><span class="p">&gt;(</span><span class="n">star</span><span class="p">));</span> <span class="k">return</span> <span class="nf">AcceptedAtAction</span><span class="p">(</span><span class="s">"Get"</span><span class="p">,</span> <span class="s">"Jobs"</span><span class="p">,</span> <span class="k">new</span> <span class="p">{</span><span class="n">id</span> <span class="p">=</span> <span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">});</span> <span class="p">}</span> </code></pre></div></div> <h2 id="performing-the-action">Performing The Action</h2> <p>As an observant reader, I’m sure you noticed that we actually aren’t doing anything with the job yet. It’s just storing the Job document in Couchbase, but it never actually processes the job and the <code class="language-plaintext highlighter-rouge">status</code> will remain <code class="language-plaintext highlighter-rouge">Pending</code>.</p> <p>For the next step, we need to make use of our message bus. Of course, we could just start the job on a background thread as part of <code class="language-plaintext highlighter-rouge">CreateStarJobAsync</code>. However, this could result in unbalanced load. Due to requirement #6, we’re going to eventually put limiters in place so that an application instance won’t process too many jobs at once. What would happen if the instance that received the POST request was already at the limit, but some other instance still had available capacity? We’d prefer for the work to be picked up on the other instance. Starting the job via the message bus separates the receiving instance from the processing instance, but still maintains a fast response time for requirement #3.</p> <p>At this point, I’ll assume that the application has a simple <code class="language-plaintext highlighter-rouge">MessageBus</code> class that acts as an abstraction for sending and receiving these messages. First, we add some logic to <code class="language-plaintext highlighter-rouge">JobService</code>.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">Job</span><span class="p">&gt;</span> <span class="nf">CreateStarJobAsync</span><span class="p">(</span><span class="n">Star</span> <span class="n">star</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">job</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Job</span> <span class="p">{</span> <span class="n">CreateStar</span> <span class="p">=</span> <span class="n">star</span><span class="p">,</span> <span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Queued</span> <span class="p">};</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">CreateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">);</span> <span class="c1">// We're adding this line to the existing method</span> <span class="nf">QueueJob</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">);</span> <span class="k">return</span> <span class="n">job</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// And adding these methods below</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">QueueJob</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="n">_messageBus</span><span class="p">.</span><span class="nf">SendMessage</span><span class="p">(</span><span class="k">new</span> <span class="n">Message</span> <span class="p">{</span><span class="n">JobId</span> <span class="p">=</span> <span class="n">id</span><span class="p">});</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ProcessNextJobAsync</span><span class="p">(</span><span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_messageBus</span><span class="p">.</span><span class="nf">ReceiveMessage</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">message</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="nf">ExecuteJobAsync</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="n">JobId</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ExecuteJobAsync</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">token</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Reload the document to make sure it's still pending</span> <span class="kt">var</span> <span class="n">job</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">GetJobAsync</span><span class="p">(</span><span class="n">id</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">==</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Complete</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Update the status to Running</span> <span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Running</span><span class="p">;</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">UpdateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span> <span class="c1">// We're just emulating a long running job here, so just delay</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">45</span><span class="p">),</span> <span class="n">token</span><span class="p">);</span> <span class="c1">// To emulate a failed job, either throw an exception here</span> <span class="c1">// Or stop the app before the delay above is reached</span> <span class="c1">// Finish creating the star</span> <span class="k">await</span> <span class="n">_starRepository</span><span class="p">.</span><span class="nf">CreateStarAsync</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">CreateStar</span><span class="p">);</span> <span class="c1">// Update the job status document</span> <span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Complete</span><span class="p">;</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">UpdateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromDays</span><span class="p">(</span><span class="m">1</span><span class="p">));</span> <span class="p">}</span> </code></pre></div></div> <p>Now, we add a <code class="language-plaintext highlighter-rouge">JobProcessor</code> class that loops and calls <code class="language-plaintext highlighter-rouge">ProcessNextJobAsync</code> until disposed.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">JobProcessor</span> <span class="p">:</span> <span class="n">IDisposable</span> <span class="p">{</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">JobService</span> <span class="n">_jobService</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">JobProcessor</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">CancellationTokenSource</span> <span class="n">_cts</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationTokenSource</span><span class="p">();</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">_started</span><span class="p">;</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">_disposed</span><span class="p">;</span> <span class="k">public</span> <span class="nf">JobProcessor</span><span class="p">(</span><span class="n">JobService</span> <span class="n">jobService</span><span class="p">,</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">JobProcessor</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span> <span class="p">{</span> <span class="n">_jobService</span> <span class="p">=</span> <span class="n">jobService</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">jobService</span><span class="p">));</span> <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">logger</span><span class="p">));</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">_disposed</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ObjectDisposedException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">JobProcessor</span><span class="p">));</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(!</span><span class="n">_started</span><span class="p">)</span> <span class="p">{</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">Poll</span><span class="p">);</span> <span class="n">_started</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Dispose</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">_disposed</span><span class="p">)</span> <span class="p">{</span> <span class="n">_cts</span><span class="p">.</span><span class="nf">Cancel</span><span class="p">();</span> <span class="n">_disposed</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Poll</span><span class="p">()</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(!</span><span class="n">_cts</span><span class="p">.</span><span class="n">IsCancellationRequested</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">await</span> <span class="n">_jobService</span><span class="p">.</span><span class="nf">ProcessNextJobAsync</span><span class="p">(</span><span class="n">_cts</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">OperationCanceledException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Ignore</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span> <span class="p">{</span> <span class="n">_logger</span><span class="p">.</span><span class="nf">LogError</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">"Unhandled exception in JobPoller"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And, finally, we start the poller when the application starts and dispose it when stopping by adding these lines to <code class="language-plaintext highlighter-rouge">Startup</code> in the <code class="language-plaintext highlighter-rouge">Configure</code> method.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">jobProcessor</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="n">ApplicationServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">JobProcessor</span><span class="p">&gt;();</span> <span class="n">jobProcessor</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span> <span class="kt">var</span> <span class="n">appLifetime</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="n">ApplicationServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">IApplicationLifetime</span><span class="p">&gt;();</span> <span class="n">appLifetime</span><span class="p">.</span><span class="n">ApplicationStopping</span><span class="p">.</span><span class="nf">Register</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="n">jobProcessor</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span> <span class="p">});</span> </code></pre></div></div> <p>Now we’ve addressed requirement #3 (start processing quickly), requirement #4 (load balance the processing across instances), and requirement #7 (the ApplicationStopping event will dispose the JobProcessor which then cancels any current processing via the CancellationToken).</p> <h2 id="preventing-duplicate-processing">Preventing Duplicate Processing</h2> <p>When working with an At Least Once message bus, there’s the possibility that two instances may receive the same message. Additionally, when we implement resumption of failed jobs for requirement #8 there will be an even greater risk of duplicate processing. However, having the same job processed at the same time by more than one instance is usually a bad thing.</p> <p>We need to use a distributed <a href="https://en.wikipedia.org/wiki/Mutual_exclusion">mutex</a> to ensure that only one instance may be working on a job at a time. When we start processing the job, we’ll request a mutex. If it’s denied, then the job is in process somewhere else and we skip it. If the mutex is issued, we hold it until we get done processing the job.</p> <p><strong>Note:</strong> This logic uses the <a href="https://www.nuget.org/packages/Couchbase.Extensions.Locks">Couchbase.Extensions.Locks</a> package, which <a href="/couchbase/2018/11/04/couchbaselocks.html">I’ve blogged about before</a>.</p> <p>First, we add this method to <code class="language-plaintext highlighter-rouge">JobRepository</code>:</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">ICouchbaseMutex</span><span class="p">&gt;</span> <span class="nf">LockJobAsync</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">,</span> <span class="n">TimeSpan</span> <span class="n">expiration</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_bucket</span><span class="p">.</span><span class="nf">RequestMutexAsync</span><span class="p">(</span><span class="n">Job</span><span class="p">.</span><span class="nf">GetKey</span><span class="p">(</span><span class="n">id</span><span class="p">),</span> <span class="n">expiration</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>Then, we alter the <code class="language-plaintext highlighter-rouge">ExecuteJobAsync</code> method in <code class="language-plaintext highlighter-rouge">JobService</code> to wrap the work in the mutex.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ExecuteJobAsync</span><span class="p">(</span><span class="kt">long</span> <span class="n">id</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">token</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">mutex</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">LockJobAsync</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">1</span><span class="p">)))</span> <span class="p">{</span> <span class="n">mutex</span><span class="p">.</span><span class="nf">AutoRenew</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">15</span><span class="p">),</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromHours</span><span class="p">(</span><span class="m">1</span><span class="p">));</span> <span class="c1">// Once we have the lock, reload the document to make sure it's still pending</span> <span class="kt">var</span> <span class="n">job</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">GetJobAsync</span><span class="p">(</span><span class="n">id</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">==</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Complete</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Update the status to Running</span> <span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Running</span><span class="p">;</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">UpdateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span> <span class="c1">// We're just emulating a long running job here, so just delay</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">45</span><span class="p">),</span> <span class="n">token</span><span class="p">);</span> <span class="c1">// To emulate a failed job, either throw an exception here</span> <span class="c1">// Or stop the app before the delay above is reached</span> <span class="c1">// Finish creating the star</span> <span class="k">await</span> <span class="n">_starRepository</span><span class="p">.</span><span class="nf">CreateStarAsync</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">CreateStar</span><span class="p">);</span> <span class="c1">// Update the job status document</span> <span class="n">job</span><span class="p">.</span><span class="n">Status</span> <span class="p">=</span> <span class="n">JobStatus</span><span class="p">.</span><span class="n">Complete</span><span class="p">;</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">UpdateJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromDays</span><span class="p">(</span><span class="m">1</span><span class="p">));</span> <span class="p">}</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">CouchbaseLockUnavailableException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Ignore and skip processing</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p><strong>Note:</strong> It’s particularly important to reload the Job from the data store and check its status before processing. This should be done within the mutex in case another instance is finishing the job just as this instance is trying to start the job.</p> <h2 id="preventing-oversubscription">Preventing Oversubscription</h2> <p>Requirement #6 states that a single application instance shouldn’t oversubscribe and process too many jobs at once. If an instance were to do this, too many CPU cycles could be spent on job processing to the detriment of HTTP response time.</p> <p>The current implementation already limits an instance to one job at a time, but let’s say we wanted to make it 2. This change to <code class="language-plaintext highlighter-rouge">JobProcessor</code> solves it.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">readonly</span> <span class="n">SemaphoreSlim</span> <span class="n">_concurrencyLimiter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SemaphoreSlim</span><span class="p">(</span><span class="m">2</span><span class="p">);</span> <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Poll</span><span class="p">()</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(!</span><span class="n">_cts</span><span class="p">.</span><span class="n">IsCancellationRequested</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">await</span> <span class="n">_concurrencyLimiter</span><span class="p">.</span><span class="nf">WaitAsync</span><span class="p">(</span><span class="n">_cts</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span> <span class="cp">#pragma warning disable 4014 </span> <span class="n">_jobService</span><span class="p">.</span><span class="nf">ProcessNextJobAsync</span><span class="p">(</span><span class="n">_cts</span><span class="p">.</span><span class="n">Token</span><span class="p">)</span> <span class="p">.</span><span class="nf">ContinueWith</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="n">_concurrencyLimiter</span><span class="p">.</span><span class="nf">Release</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">IsFaulted</span><span class="p">)</span> <span class="p">{</span> <span class="n">_logger</span><span class="p">.</span><span class="nf">LogError</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">Exception</span><span class="p">,</span> <span class="s">"Unhandled exception in JobPoller"</span><span class="p">);</span> <span class="p">}</span> <span class="p">},</span> <span class="n">_cts</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span> <span class="cp">#pragma warning restore 4014 </span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">OperationCanceledException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Ignore</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span> <span class="p">{</span> <span class="n">_logger</span><span class="p">.</span><span class="nf">LogError</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">"Unhandled exception in JobPoller"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Of course, better yet the limit should be configurable using <code class="language-plaintext highlighter-rouge">IOptions&lt;T&gt;</code>!</p> <h2 id="resuming-jobs">Resuming Jobs</h2> <p>At this point, the only requirement we’re missing is #8, resuming failed jobs on another instance. However, it’s one of the most important requirements! Modern cloud infrastructures must be built to be highly fault tolerant, and should built on the premise of <em>when</em> a failure happens, not <em>if</em>.</p> <p>To address resuming, we’ll have a single application instance run a query every minute looking for jobs that are not flagged as <code class="language-plaintext highlighter-rouge">Completed</code>. It will test for a mutex on each job to see if it’s already running, and if not refire the message into the message bus. A mutex issued to a failed application instance will eventually expire, causing the message to fire and another available instance to resume the work.</p> <p>We’ll also use another mutex for this process itself, so that only one instance tries it every minute. This will prevent every instance from firing multiple events for the same job at the same time.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">JobRecoveryPoller</span> <span class="p">:</span> <span class="n">IDisposable</span> <span class="p">{</span> <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">TimeSpan</span> <span class="n">PollInterval</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">1</span><span class="p">);</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">JobService</span> <span class="n">_jobService</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">JobRepository</span> <span class="n">_jobRepository</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">JobRecoveryPoller</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">IBucket</span> <span class="n">_bucket</span><span class="p">;</span> <span class="k">private</span> <span class="k">readonly</span> <span class="n">CancellationTokenSource</span> <span class="n">_cts</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationTokenSource</span><span class="p">();</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">_started</span><span class="p">;</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">_disposed</span><span class="p">;</span> <span class="k">public</span> <span class="nf">JobRecoveryPoller</span><span class="p">(</span><span class="n">JobService</span> <span class="n">jobService</span><span class="p">,</span> <span class="n">JobRepository</span> <span class="n">jobRepository</span><span class="p">,</span> <span class="n">IDefaultBucketProvider</span> <span class="n">bucketProvider</span><span class="p">,</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">JobRecoveryPoller</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span> <span class="p">{</span> <span class="n">_jobService</span> <span class="p">=</span> <span class="n">jobService</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">jobService</span><span class="p">));</span> <span class="n">_jobRepository</span> <span class="p">=</span> <span class="n">jobRepository</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">jobRepository</span><span class="p">));</span> <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">logger</span><span class="p">));</span> <span class="n">_bucket</span> <span class="p">=</span> <span class="n">bucketProvider</span><span class="p">.</span><span class="nf">GetBucket</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Start</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">_disposed</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ObjectDisposedException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">JobProcessor</span><span class="p">));</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(!</span><span class="n">_started</span><span class="p">)</span> <span class="p">{</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">Poll</span><span class="p">);</span> <span class="n">_started</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">Dispose</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">_disposed</span><span class="p">)</span> <span class="p">{</span> <span class="n">_cts</span><span class="p">.</span><span class="nf">Cancel</span><span class="p">();</span> <span class="n">_disposed</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Poll</span><span class="p">()</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(!</span><span class="n">_cts</span><span class="p">.</span><span class="n">IsCancellationRequested</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="c1">// Wait for the poll interval</span> <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="n">PollInterval</span><span class="p">,</span> <span class="n">_cts</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span> <span class="c1">// Take out a lock for job recovery polling</span> <span class="c1">// Don't dispose so that it holds until it expires after PollInterval</span> <span class="c1">// This will ensure only one instance of the app polls every poll interval</span> <span class="k">await</span> <span class="n">_bucket</span><span class="p">.</span><span class="nf">RequestMutexAsync</span><span class="p">(</span><span class="s">"jobRecoveryPoller"</span><span class="p">,</span> <span class="n">PollInterval</span><span class="p">);</span> <span class="kt">var</span> <span class="n">jobs</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_jobRepository</span><span class="p">.</span><span class="nf">GetIncompleteJobsAsync</span><span class="p">();</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">job</span> <span class="k">in</span> <span class="n">jobs</span><span class="p">)</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="c1">// Try to lock the job to see if it's being processed currently</span> <span class="k">using</span> <span class="p">(</span><span class="n">_jobRepository</span><span class="p">.</span><span class="nf">LockJobAsync</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">1</span><span class="p">)))</span> <span class="p">{</span> <span class="p">}</span> <span class="c1">// Make sure we've unlocked the job before we get here</span> <span class="c1">// And fire events into the message bus for the unhandled job</span> <span class="c1">// This allows any instance with capacity to pick up the job</span> <span class="n">_jobService</span><span class="p">.</span><span class="nf">QueueJob</span><span class="p">(</span><span class="n">job</span><span class="p">.</span><span class="n">Id</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">CouchbaseLockUnavailableException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Job is already being processed, ignore</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">OperationCanceledException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Ignore</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">CouchbaseLockUnavailableException</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Unable to lock for singleton job recovery poller process, ignore</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span> <span class="p">{</span> <span class="n">_logger</span><span class="p">.</span><span class="nf">LogError</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">"Unhandled exception in JobRecoveryPoller"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>We just need to alter our startup code again to start and dispose the recovery poller.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">jobProcessor</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="n">ApplicationServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">JobProcessor</span><span class="p">&gt;();</span> <span class="n">jobProcessor</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span> <span class="kt">var</span> <span class="n">jobRecoveryPoller</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="n">ApplicationServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">JobRecoveryPoller</span><span class="p">&gt;();</span> <span class="n">jobRecoveryPoller</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span> <span class="kt">var</span> <span class="n">appLifetime</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="n">ApplicationServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">IApplicationLifetime</span><span class="p">&gt;();</span> <span class="n">appLifetime</span><span class="p">.</span><span class="n">ApplicationStopping</span><span class="p">.</span><span class="nf">Register</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="n">jobProcessor</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span> <span class="n">jobRecoveryPoller</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span> <span class="p">});</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>As you can see, handling long running jobs in a microservice architecture can be a bit tricky. Hopefully, having this pattern to use as a starting point will make things easier for you.</p> <p>Here are a few improvements to consider in a real world implementation. I left these off intentionally so that I could focus on the key aspects of the requirements.</p> <ul> <li>Support for multiple different types of jobs</li> <li>Interfaces for the repositories and services at allow for mocking in unit tests</li> <li>An actual message bus (duh!)</li> <li>Put the pattern into a reusable Nuget package (If there’s enough interest, I might do this myself)</li> </ul> <p>If anyone sees any problems with my code or approach, please let me know so I can make updates.</p> <p class="notice--info"><strong>Note:</strong> The completed and runnable example is available at <a href="https://github.com/brantburnett/CouchbaseRestfulJobPattern">https://github.com/brantburnett/CouchbaseRestfulJobPattern</a>.</p>Brant BurnettThis blog is one of The December 6th entries on the 2018 C# Advent Calendar. Thanks for having me again Matt!Using Amazon Elastic Container Service for Kubernetes (EKS) on Windows 102018-11-08T14:45:00+00:002018-11-08T14:45:00+00:00https://btburnett.com/kubernetes/aws/2018/11/08/eks-on-windows<p>At <a href="https://centeredgesoftware.com/">CenterEdge Software</a>, we currently operate our <a href="https://kubernetes.io/">Kubernetes</a> clusters on <a href="https://aws.amazon.com/">AWS</a>. We manage the clusters ourselves, using the <a href="https://github.com/kubernetes/kops">kops</a> tool. Unfortunately, managing your own Kubernetes cluster adds a lot of overhead.</p> <p>Therefore, I recently embarked on a proof of concept using <a href="https://aws.amazon.com/eks/">Amazon Elastic Container Service for Kubernetes</a>, a.k.a. EKS. I quickly found that a significant friction point in this process was my Windows 10 laptop, which is a problem since CenterEdge Software is a Microsoft shop.</p> <p>Below I share some of the steps I found that helped along the way. I won’t cover setting up the EKS cluster itself, I’ll let the <a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html">AWS documentation</a> handle that.</p> <h2 id="prerequisites">Prerequisites</h2> <ol> <li> <p>Install <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">kubectl</a> and the <a href="https://aws.amazon.com/cli/">AWS CLI</a>. I used <a href="https://chocolatey.org/">Chocolatey</a> to install both.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nf">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nt">-y</span><span class="w"> </span><span class="nx">kubernetes-cli</span><span class="w"> </span><span class="nf">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nt">-y</span><span class="w"> </span><span class="nx">awscli</span><span class="w"> </span></code></pre></div> </div> </li> <li> <p>Configure AWS CLI with your credentials</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nf">aws</span><span class="w"> </span><span class="nx">configure</span><span class="w"> </span></code></pre></div> </div> </li> </ol> <h2 id="setting-up-aws-iam-authenticator">Setting Up AWS IAM Authenticator</h2> <p>When using EKS, kubectl must be configured to use the <a href="https://github.com/kubernetes-sigs/aws-iam-authenticator">AWS IAM Authenticator</a>. This lightweight utility is called by kubectl to get authentication tokens, and uses your credentials configured for the AWS CLI. It can support IAM roles and multiple profiles, but for this example I’ll keep it simple and assume we’re using the default profile configured via <code class="language-plaintext highlighter-rouge">aws configure</code>.</p> <ol> <li>Download the authenticator. The current URL for Windows is https://amazon-eks.s3-us-west-2.amazonaws.com/1.11.5/2018-12-06/bin/windows/amd64/aws-iam-authenticator.exe, but you may want to find the up-to-date version <a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html">here</a>.</li> <li>Place aws-iam-authenticator.exe somewhere in your system path. For example, I was lazy and put it in C:\ProgramData\Chocolatey\bin.</li> <li>Right-click on aws-iam-authenticator.exe, select Properties, and Unblock the file so it can be executed.</li> <li> <p>Confirm that the command is working from a new shell window:</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nf">aws-iam-authenticator</span><span class="w"> </span><span class="nt">--help</span><span class="w"> </span></code></pre></div> </div> </li> </ol> <p><strong>Update</strong>: I updated some of the links above, and here is a script to download aws-iam-authenticator and add it to your path:</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$installDir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">${env:LOCALAPPDATA}</span><span class="s2">\aws-iam-authenticator"</span><span class="w"> </span><span class="nv">$version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"1.11.5/2018-12-06"</span><span class="w"> </span><span class="nf">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nv">$installDir</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="nf">Invoke-WebRequest</span><span class="w"> </span><span class="nt">-OutFile</span><span class="w"> </span><span class="nv">$installDir</span><span class="nx">\aws-iam-authenticator.exe</span><span class="w"> </span><span class="nt">-UseBasicParsing</span><span class="w"> </span><span class="s2">"https://amazon-eks.s3-us-west-2.amazonaws.com/</span><span class="nv">$version</span><span class="s2">/bin/windows/amd64/aws-iam-authenticator.exe"</span><span class="w"> </span><span class="nf">Unblock-File</span><span class="w"> </span><span class="nv">$installDir</span><span class="nx">\aws-iam-authenticator.exe</span><span class="w"> </span><span class="nv">$path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="kt">Environment</span><span class="p">]::</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s2">"PATH"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="kt">EnvironmentVariableTarget</span><span class="p">]::</span><span class="nf">User</span><span class="p">)</span><span class="w"> </span><span class="o">-split</span><span class="w"> </span><span class="s2">";"</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$path</span><span class="w"> </span><span class="o">-inotcontains</span><span class="w"> </span><span class="nv">$installDir</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$path</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$installDir</span><span class="w"> </span><span class="p">[</span><span class="kt">Environment</span><span class="p">]::</span><span class="nf">SetEnvironmentVariable</span><span class="p">(</span><span class="s2">"PATH"</span><span class="p">,</span><span class="w"> </span><span class="nv">$path</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="s2">";"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="kt">EnvironmentVariableTarget</span><span class="p">]::</span><span class="nf">User</span><span class="p">)</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">PATH</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(([</span><span class="kt">Environment</span><span class="p">]::</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s2">"PATH"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="kt">EnvironmentVariableTarget</span><span class="p">]::</span><span class="nf">Machine</span><span class="p">)</span><span class="w"> </span><span class="o">-split</span><span class="w"> </span><span class="s2">";"</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$path</span><span class="p">)</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="s2">";"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h2 id="adding-your-cluster-to-your-kubernetes-config">Adding Your Cluster To Your Kubernetes Config</h2> <p>The easiest way to add your cluster to your Kubernetes configuration is using the AWS CLI. It’s also possible to keep multiple configuration files, but I prefer having multiple contexts inside my default configuration file.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Substitute "brant" below with the name of your EKS cluster</span><span class="w"> </span><span class="nf">aws</span><span class="w"> </span><span class="nx">eks</span><span class="w"> </span><span class="nx">update-kubeconfig</span><span class="w"> </span><span class="nt">--name</span><span class="w"> </span><span class="nx">brant</span><span class="w"> </span></code></pre></div></div> <p>However, after this is complete I recommend changing the name of the created context to be more usable. The first parameter below is the name of the context output by the update-kubeconfig command. The second is the new name.</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">kubectl</span><span class="w"> </span><span class="nx">config</span><span class="w"> </span><span class="nx">rename-context</span><span class="w"> </span><span class="nx">arn:aws:eks:us-east-1:000000000000:cluster/brant</span><span class="w"> </span><span class="nx">brant</span><span class="w"> </span></code></pre></div></div> <p>Finally, test it out!</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">kubectl</span><span class="w"> </span><span class="nx">get</span><span class="w"> </span><span class="nx">svc</span><span class="w"> </span></code></pre></div></div> <h2 id="working-from-ubuntu-on-windows-using-wsl">Working From Ubuntu on Windows using WSL</h2> <p>Unfortunately, many tools you may wish to use are Linux tools and don’t work well from Windows. An easy solution is to <a href="https://www.microsoft.com/en-us/p/ubuntu/9nblggh4msv6?activetab=pivot:overviewtab">install Ubuntu on Windows 10</a>. However, making your previous configuration for EKS work in Ubuntu requires a few more steps.</p> <ol> <li>Install [kubectl]https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-using-native-package-management) in Ubuntu.</li> <li> <p>Add a KUBECONFIG environment variable to your Windows user profile (alter the path below if needed):</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nf">setx</span><span class="w"> </span><span class="nx">KUBECONFIG</span><span class="w"> </span><span class="nv">${env:USERPROFILE}</span><span class="nx">\.kube\config</span><span class="w"> </span></code></pre></div> </div> </li> <li> <p>Configure WSL to pass KUBECONFIG into Ubuntu, <a href="https://blogs.msdn.microsoft.com/commandline/2017/12/22/share-environment-vars-between-wsl-and-windows/">while remapping the path</a>:</p> <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="c"># WSLENV is a colon-separated list of environment variables to copy to Ubuntu from your Windows Profile</span><span class="w"> </span><span class="c"># Appending "/p" to the variable name tells WSL that the variable is a path, and to remap the path to the Ubuntu path when it's copied</span><span class="w"> </span><span class="nf">setx</span><span class="w"> </span><span class="nx">WSLENV</span><span class="w"> </span><span class="err">$</span><span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="no">System.</span><span class="kt">String</span><span class="p">]::</span><span class="nf">IsNullOrWhitespace</span><span class="p">(</span><span class="nv">${env:WSLENV}</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">""</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">${env:WSLENV}</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">":"</span><span class="p">})</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">"KUBECONFIG/p"</span><span class="p">)</span><span class="w"> </span></code></pre></div> </div> </li> <li>Restart Ubuntu</li> <li> <p>Test it out. Kubectl will run in Ubuntu, which in turn executes the Windows aws-iam-authenticator.exe process to get the authentication token.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> kubectl get svc </code></pre></div> </div> </li> </ol> <h2 id="conclusion">Conclusion</h2> <p>At this point, you should have complete access to your EKS cluster via kubectl from both Powershell and Ubuntu Bash. Now the real fun can begin!</p>Brant BurnettAt CenterEdge Software, we currently operate our Kubernetes clusters on AWS. We manage the clusters ourselves, using the kops tool. Unfortunately, managing your own Kubernetes cluster adds a lot of overhead.Concurrency, Locks, the Cloud, and Couchbase2018-11-04T15:30:00+00:002018-11-04T15:30:00+00:00https://btburnett.com/couchbase/2018/11/04/couchbaselocks<p>In an application that supports parallel processing, concurrency management is almost invariably required. There are many forms of parallel processing, including multithreading, multiple processes, or multiple servers. In the case of multithreading, different languages/frameworks will generally provide highly efficient in-process locks. For multiple process scenarios, such as NodeJS using the cluster module, there are various options which use interprocess communication.</p> <h2 id="what-about-multiple-servers">What About Multiple Servers?</h2> <p>These days it feels like every application is designed to run in the cloud. Applications designed for the cloud are built, from the ground up, to support running multiple copies of the application on multiple servers. Or at least, they should be. If you’re building a cloud app that isn’t designed for horizontal scaling, I’ll go out on a limb and say you’re doing it wrong.</p> <p>So how should a distributed application handle locks and concurrency? Many scenarios can still take advantage of in-process locks. For example, <a href="https://github.com/couchbaselabs/Linq2Couchbase">Linq2Couchbase</a> uses in-process locks to cache reflection results to improve performance. Since the cache is kept separately in-process on each server, the locks may be in-process. In fact, they should be in-process whenever possible to maintain performance.</p> <p>However, these are not the only concurrency scenarios. In my work on microservices and other cloud architectures, I have frequently encountered scenarios where distributed locks are preferable or required. For example:</p> <ul> <li>Refreshing/managing shared caches stored in a cache cluster. If there are multiple requests that miss the cache at the same time, it may be desirable to have only one server/request refresh the cache to keep load low.</li> <li>Managing distributed jobs. For example, only one server in the cluster may run a job at once, but the servers “negotiate” which server runs the job via locks. Lock expiration may also be used to resume the job if a server fails unexpectedly.</li> <li>Scheduled task management. A task may need to run once an hour, but only one server in your cluster should execute the task.</li> </ul> <h2 id="distributed-locks-and-couchbase">Distributed Locks and Couchbase</h2> <p>There are many solutions available for distributed locks, but at <a href="https://centeredgesoftware.com/">CenterEdge Software</a> we specifically wanted an option powered by Couchbase. We already use Couchbase as our primary data store in the cloud, so using Couchbase avoids the need to spin up an additional HA cluster just to manage locks.</p> <p>Couchbase’s <a href="https://en.wikipedia.org/wiki/Couchbase_Server#Architecture">CP architecture</a> makes it ideal for managing distributed locks. Because each document has a single server responsible for it, and updates are atomic, we can safely assume that two servers cannot mutate the same document simultaneously. There may be edge cases during server failover, but during normal operations, or even rebalance operations, consistency will be maintained.</p> <h2 id="couchbaseextensionslocks">Couchbase.Extensions.Locks</h2> <p>To help manage distributed locks in Couchbase, I created the <a href="https://www.nuget.org/packages/Couchbase.Extensions.Locks/1.0.0-beta">Couchbase.Extensions.Locks</a> NuGet package. This package has kindly been adopted by the Couchbase SDK team (thanks guys!) so that all C# developers can take advantage of this lightweight abstraction layer. It’s currently in beta to give the community a chance to perform testing, but it is running in production successfully at CenterEdge Software today.</p> <p>At this time, Couchbase.Extensions.Locks only supports mutexes, a.k.a. <a href="https://en.wikipedia.org/wiki/Mutual_exclusion">Mutual Exclusion</a>. This assures developers that only one thread of one application instance is within a critical section at a time. Future iterations may add other forms of concurrency control.</p> <p>The supplied mutexes also require an expiration. This protects against indefinite locks due to application failure. There are built-in utilities to assist with renewing the mutex if your process is long running.</p> <div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">mutex</span> <span class="p">=</span> <span class="k">await</span> <span class="n">bucket</span><span class="p">.</span><span class="nf">RequestMutexAsync</span><span class="p">(</span><span class="s">"my-lock-name"</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">10</span><span class="p">)</span> <span class="cm">/* expiration */</span><span class="p">))</span> <span class="p">{</span> <span class="n">mutex</span><span class="p">.</span><span class="nf">AutoRenew</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">5</span><span class="p">)</span> <span class="cm">/* renewal interval */</span><span class="p">,</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">1</span><span class="p">)</span> <span class="cm">/* maximum lifetime */</span><span class="p">);</span> <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Do some work here</span> <span class="c1">// This lock will be held until the end of the using statement,</span> <span class="c1">// so long as the total loop time is less than one minute</span> <span class="c1">// If the application crashes, the lock will be released within 10 seconds.</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Complete <a href="https://github.com/couchbaselabs/Couchbase.Extensions/blob/master/docs/locks.md">instructions are available in the GitHub repository</a>, including several usage patterns. This includes a <a href="https://github.com/couchbaselabs/Couchbase.Extensions/blob/master/docs/locks.md#retrying-locks">pattern for retries</a> in case the lock is unavailable initially. In this case I chose a pattern using <a href="https://github.com/App-vNext/Polly">Polly</a>, I intentionally did not build the pattern into the Locks package. This allows developers to choose their favorite retry pattern and library, without introducing a specific dependency into the Locks package.</p> <p>Using an memcached bucket for locks, or ephemeral for Couchbase Server Enterprise Edition 5.0 or later, is preferable. There generally isn’t a need for lock documents to be persisted to disk, so this avoids unnecessary overhead on the cluster. If the bucket is configured for NRU (not recently used) ejection, make sure the locks are short-lived or have a fast renewal rate. This will ensure they aren’t ejected, thereby releasing the lock, unexpectedly.</p> <h2 id="conclusion">Conclusion</h2> <p>For C# developers, there is now a standard package which delivers simple and lightweight distributed mutexes via a Couchbase cluster. I’m hopeful that that developers using other languages, such as Node or Go, may make similar packages using the same document schema. This would allow locks to be shared between any microservice in an application, regardless of language.</p> <p>Please feel free to <a href="https://github.com/couchbaselabs/Couchbase.Extensions/issues">file GitHub issues</a> if you find any bugs or have suggestions, and let me know on <a href="https://twitter.com/btburnett3">Twitter</a> if you like it! We’ll probably roll an official release after we’ve collected some feedback and fixed any issues.</p>Brant BurnettIn an application that supports parallel processing, concurrency management is almost invariably required. There are many forms of parallel processing, including multithreading, multiple processes, or multiple servers. In the case of multithreading, different languages/frameworks will generally provide highly efficient in-process locks. For multiple process scenarios, such as NodeJS using the cluster module, there are various options which use interprocess communication.