Jekyll2020-12-06T17:48:16+00:00https://www.vivienfabing.com/feed.xmlVivien Fabing’s blogVivien Fabing on Programming, .NET, Azure, DevOps and More. Microsoft MVP (Developer Technologies).Vivien FabingASP.NET Core: Simple shared DTOs (and Clients) with React2020-12-06T11:37:00+00:002020-12-06T11:37:00+00:00https://www.vivienfabing.com/react/2020/12/06/aspnetcore-simple-shared-dtos-with-react<p>With the recent release of <code class="language-plaintext highlighter-rouge">ASP.NET Core 5</code>, <a href="https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-5.0?WT.mc_id=DOP-MVP-5003680&view=aspnetcore-5.0#openapi-specification-on-by-default">OpenAPI documents generation</a> for describing our Web API is now included by default when we create a new Web API project.</p>
<p>This is definitely a great news as I always considered the addition of this feature (<em>mainly by adding the <code class="language-plaintext highlighter-rouge">Swashbuckle.AspNetCore</code> nuget package</em>) as a “must have” for all Web API projects.</p>
<p>Now let’s not stop at documenting our Web API only, and let’s go one step further:<br />
What if we could generate <strong>automatically</strong> some boilerplate code such as our <code class="language-plaintext highlighter-rouge">DTOs</code> used in our APIs, and even better, generate our <code class="language-plaintext highlighter-rouge">TypeScript</code> web <code class="language-plaintext highlighter-rouge">Clients</code> <strong>automatically</strong>?</p>
<p>Well yes we can obviously as described in the <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?WT.mc_id=DOP-MVP-5003680&view=aspnetcore-5.0&tabs=visual-studio#code-generation">official documention</a>, but let me walk through you an example.</p>
<h1 id="start-generating-the-code">Start generating the code</h1>
<p>As described in the documentation, many tools are at our disposal to generate our <code class="language-plaintext highlighter-rouge">TypeScript</code> boilerplate code.</p>
<p>To start with, I highly recommend playing with the graphical tool <a href="https://github.com/RicoSuter/NSwag/wiki/NSwagStudio">NSwagStudio</a></p>
<p>Once installed, you can first define where is located your <code class="language-plaintext highlighter-rouge">OpenAPI</code> document (<em>or more historically called <code class="language-plaintext highlighter-rouge">swagger</code> document</em>):<br />
<img src="/assets/2020-12-06/01-nswag-studio-define-your-open-api-swagger-url.png" alt="01-nswag-studio-define-your-open-api-swagger-url" /></p>
<p>And then configure your preferences of how your <code class="language-plaintext highlighter-rouge">DTOs</code> and <code class="language-plaintext highlighter-rouge">Clients</code> will be generated. Here is an example of the configuration I use:
<img src="/assets/2020-12-06/02-nswag-configuration-for-typescript-code-generation-using-axios.png" alt="02-nswag-configuration-for-typescript-code-generation-using-axios" /></p>
<p>As described in the previous articles, I use <code class="language-plaintext highlighter-rouge">axios</code> Clients even though the generation is still in preview as described in the <a href="https://github.com/RicoSuter/NSwag">documentation</a>.<br />
I also set the <code class="language-plaintext highlighter-rouge">Generation Mode</code> to <code class="language-plaintext highlighter-rouge">MultipleClientsFromFirstTagAndOperationId</code>, which, by looking at the <a href="https://swagger.io/specification/#operation-object">OpenAPI specification</a> and how the <a href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#add-tag-metadata">Tag</a> and the <a href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#assign-explicit-operationids">OperationId</a> are generated, should produce 1 client per <code class="language-plaintext highlighter-rouge">Controller</code> (<em>by default</em>) and 1 method by <code class="language-plaintext highlighter-rouge">Endpoint</code>.</p>
<p><img src="/assets/2020-12-06/03-nswag-typescript-client-code-generation.png" alt="03-nswag-typescript-client-code-generation" /></p>
<p>I also specify to produce <code class="language-plaintext highlighter-rouge">DTOs</code> as <code class="language-plaintext highlighter-rouge">TypeScript</code> interfaces and also an output file name that I will be able to use in my application.</p>
<h1 id="use-the-produced-code-in-our-app">Use the produced code in our app</h1>
<p>This part should be pretty straightforward.<br />
First, we can remove our manually written <code class="language-plaintext highlighter-rouge">DTOs</code> in the <code class="language-plaintext highlighter-rouge">useAxios</code> custom hook to use the <code class="language-plaintext highlighter-rouge">DTOs</code> produced by nswag:</p>
<p><img src="/assets/2020-12-06/04-use-nswag-produced-dtos.png" alt="04-use-nswag-produced-dtos" /></p>
<p>Then in our components, we will be able to use the generated strongly typed <code class="language-plaintext highlighter-rouge">TypeScript Clients</code></p>
<p><img src="/assets/2020-12-06/05-use-nswag-produced-strongly-typed-typescript-clients.png" alt="05-use-nswag-produced-strongly-typed-typescript-clients" /></p>
<p>And that’s all. Using APIs in the future should be much easier as the strongly typed <code class="language-plaintext highlighter-rouge">TypeScript Clients</code> should guide us pretty well in calling correctly the APIs using the right URLs and parameters.</p>
<h1 id="nswag-clients-automated-generation">NSWAG Clients automated generation</h1>
<p>Alright, another very complicated part. Now that we could check our process, let’s see how to update our generated <code class="language-plaintext highlighter-rouge">Clients</code>, and of course, how to do it <code class="language-plaintext highlighter-rouge">automatically</code>.</p>
<p>Fortunately, if you installed <code class="language-plaintext highlighter-rouge">NSwagStudio</code> (<em>using <a href="https://chocolatey.org/">chocolatey</a> for instance</em>), the <code class="language-plaintext highlighter-rouge">nswag.exe</code> command line tool is probably already installed and available in your <code class="language-plaintext highlighter-rouge">PATH</code>, so the only things we need to do is to make sure that our <code class="language-plaintext highlighter-rouge">nswag definition</code> is saved in a file called <code class="language-plaintext highlighter-rouge">nswag.json</code> for instance:<br />
<img src="/assets/2020-12-06/06-save-nswag-definition-file.png" alt="06-save-nswag-definition-file" /></p>
<p>Start our api (<em>using <code class="language-plaintext highlighter-rouge">dotnet run</code> for instance</em>) and then we just need to execute the following command line:</p>
<p><code class="language-plaintext highlighter-rouge">nswag run /runtime:NetCore31</code></p>
<p><img src="/assets/2020-12-06/07-update-nswag-generated-clients-and-dtos.gif" alt="07-update-nswag-generated-clients-and-dtos" /></p>
<blockquote>
<p><em>Note: NetCore31 being the default runtime selected when creating the <code class="language-plaintext highlighter-rouge">nswag.json</code> file.</em></p>
</blockquote>
<p>And that’s all!</p>
<h1 id="in-conclusion">In conclusion</h1>
<p>I hope this simple article gave you some hints about leveraging your <code class="language-plaintext highlighter-rouge">OpenAPI</code> documentations and hopefully save some time creating and maintaining manually all of the <code class="language-plaintext highlighter-rouge">DTOs</code> and <code class="language-plaintext highlighter-rouge">Clients</code>.</p>
<p>While this scenario was ok for me as I wanted a kind of “update when you decide” scenario, having to start the API everytime you want to update your <code class="language-plaintext highlighter-rouge">TypeScript Clients</code> might not be ideal and you might want to have a look to referencing directly the produced <code class="language-plaintext highlighter-rouge">ASP.NET Core assemblies</code> instead of referencing the <code class="language-plaintext highlighter-rouge">OpenAPI document</code> (<em>more info in the <a href="https://github.com/RicoSuter/NSwag/wiki/NSwagStudio">Github documentation</a></em>)</p>
<p>As usual, feel free to react in the comments or reply to me on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>.</p>
<p>May the code be with you!</p>Vivien FabingWith the recent release of ASP.NET Core 5, OpenAPI documents generation for describing our Web API is now included by default when we create a new Web API project.ASP.NET Core: Simple shared generic errors with React2020-11-25T11:37:00+00:002020-11-25T11:37:00+00:00https://www.vivienfabing.com/react/2020/11/25/aspnetcore-simple-shared-generic-errors-with-react<p>Given an ASP.NET Core React app, sometimes you might want to throw some errors from any API endpoint, and be able to handle them gracefully in your Front end.
For generic well known errors (401, 403, 404, etc.), the API part is pretty simple and a generic Front end handler system such as <a href="https://github.com/axios/axios#interceptors">axios interceptors</a> should do the job quite well.</p>
<p>But what if you wanted to provide some business details to enhance your generic <code class="language-plaintext highlighter-rouge">Bad Request</code> errors and be able to tell “What was bad” and how to correct it, while still keeping all this handling generic to any HTTP call in your application?</p>
<p>Well let me show you a simple version of the system implemented with the help of my great colleagues <a href="https://thomaslevesque.com/">Thomas</a> and <a href="https://blogs.infinitesquare.com/users/zkonate">Zack</a>.</p>
<h1 id="foreword-standardizing-errors-output-with-problemdetails">Foreword: Standardizing errors output with <code class="language-plaintext highlighter-rouge">ProblemDetails</code>?</h1>
<p>How are we going to handle the errors in our app? Classical question when you start a new project, isn’t it?</p>
<p>Well since ASP.NET Core 2.2, the <a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?WT.mc_id=DOP-MVP-5003680&view=aspnetcore-5.0#problem-details-for-error-status-codes">Problem Details</a> format (<em>based on the <a href="https://tools.ietf.org/html/rfc7807">RFC 7807</a></em>) became the standard response for errors (<em>for status code >= 400</em>), and this system could give us some answers our previous question.</p>
<p>Example of the JSON produced for a <code class="language-plaintext highlighter-rouge">404</code> error using a <code class="language-plaintext highlighter-rouge">ProblemDetails</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">type:</span><span class="w"> </span><span class="s2">"https://tools.ietf.org/html/rfc7231#section-6.5.4"</span><span class="p">,</span><span class="w">
</span><span class="err">title:</span><span class="w"> </span><span class="s2">"Not Found"</span><span class="p">,</span><span class="w">
</span><span class="err">status:</span><span class="w"> </span><span class="mi">404</span><span class="p">,</span><span class="w">
</span><span class="err">traceId:</span><span class="w"> </span><span class="s2">"0HLHLV31KRN83:00000001"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The main purpose of the <code class="language-plaintext highlighter-rouge">Problem Details</code> standard is to provide an <code class="language-plaintext highlighter-rouge">easy to understand</code>, <code class="language-plaintext highlighter-rouge">standard</code> way of defining our errors details.
Moreover, the implementation in .NET possesses a <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails.extensions?WT.mc_id=DOP-MVP-5003680&view=aspnetcore-5.0">ProblemDetails.Extensions</a> Property which will allow us to provide our additional details.</p>
<p>But enough talk, let’s see what the ASP.NET Core part looks like:</p>
<h1 id="aspnet-core-generic-error-handling-with-mvcfilters-and-problemdetails">ASP.NET Core generic error handling with <code class="language-plaintext highlighter-rouge">Mvc.Filters</code> and <code class="language-plaintext highlighter-rouge">ProblemDetails</code></h1>
<p>So what we want is a way to catch our exceptions and have the opportunity to customize/enhance our <code class="language-plaintext highlighter-rouge">Problem Details</code> exceptions before they are returned to the Front end.
For this, we could have implemented a custom <code class="language-plaintext highlighter-rouge">ProblemDetailsFactory</code> as suggested by the <a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?WT.mc_id=DOP-MVP-5003680&view=aspnetcore-5.0#implement-problemdetailsfactory">documentation</a>.
But since we were more familiar with the <code class="language-plaintext highlighter-rouge">Mvc.Filters</code> and didn’t encounter any problem so far (<em>and it stayed quite simple, as described in the blog title ;)</em>), I’ll show you the steps which are need for this solution:</p>
<h2 id="define-our-custom-mvcfilters">Define our custom <code class="language-plaintext highlighter-rouge">Mvc.Filters</code></h2>
<p>So basically, we just need to implement the <code class="language-plaintext highlighter-rouge">IExceptionFilter</code> interface and define the <code class="language-plaintext highlighter-rouge">OnException</code> method to return our <code class="language-plaintext highlighter-rouge">ProblemDetails</code> according to the type of <code class="language-plaintext highlighter-rouge">Exception</code> raised:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">MyAppErrorFilter</span> <span class="p">:</span> <span class="n">IExceptionFilter</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnException</span><span class="p">(</span><span class="n">ExceptionContext</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">ExceptionHandled</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</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="n">context</span><span class="p">.</span><span class="n">Exception</span> <span class="k">switch</span>
<span class="p">{</span>
<span class="n">EntityNotFoundException</span> <span class="n">_</span> <span class="p">=></span> <span class="n">context</span><span class="p">.</span><span class="nf">CreateErrorResult</span><span class="p">(</span><span class="n">ApiErrorCode</span><span class="p">.</span><span class="n">EntityNotFound</span><span class="p">),</span>
<span class="n">InvalidStatusChangeException</span> <span class="n">ex</span> <span class="p">=></span> <span class="n">context</span><span class="p">.</span><span class="nf">CreateErrorResult</span><span class="p">(</span><span class="n">ApiErrorCode</span><span class="p">.</span><span class="n">InvalidStatusChange</span><span class="p">,</span> <span class="n">additionalDetails</span><span class="p">:</span> <span class="k">new</span> <span class="p">{</span> <span class="n">ex</span><span class="p">.</span><span class="n">AllowedStatus</span> <span class="p">}),</span>
<span class="n">_</span> <span class="p">=></span> <span class="k">null</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="n">result</span><span class="p">.</span><span class="n">DeclaredType</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ProblemDetails</span><span class="p">);</span>
<span class="n">result</span><span class="p">.</span><span class="n">ContentTypes</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"application/problem+json"</span><span class="p">);</span>
<span class="n">context</span><span class="p">.</span><span class="n">Result</span> <span class="p">=</span> <span class="n">result</span><span class="p">;</span>
<span class="n">context</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">.</span><span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">BadRequest</span><span class="p">;</span>
<span class="n">context</span><span class="p">.</span><span class="n">ExceptionHandled</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">switch</code> is in charge of generating the <code class="language-plaintext highlighter-rouge">ProblemDetails</code> by calling a custom <code class="language-plaintext highlighter-rouge">CreateErrorResult</code> extension method which will be done by using the <code class="language-plaintext highlighter-rouge">ProblemDetailsFactory</code> and adding 2 custom properties:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">code</code>: An <code class="language-plaintext highlighter-rouge">ApiErrorCode</code> enum which will help us to get a simple way to define the type of error from the Front end,</li>
<li><code class="language-plaintext highlighter-rouge">additionalDetails</code>: An <code class="language-plaintext highlighter-rouge">object</code> representing any additional information to get a better understanding of the error, such as a list of <a href="https://github.com/vfabing/simple-aspnetcore-react-shared-generic-errors/blob/066aec6f3ed0f8e8c1e98f15c43deaf54f8f53e7/ErrorHandling/MyAppErrorFilter.cs#L20"><code class="language-plaintext highlighter-rouge">AllowedStatus</code> in our example</a></li>
</ul>
<p>Here is the <code class="language-plaintext highlighter-rouge">ApiErrorCode</code> enum as well as the <code class="language-plaintext highlighter-rouge">CreateErrorResult</code> extension method:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">enum</span> <span class="n">ApiErrorCode</span>
<span class="p">{</span>
<span class="n">Unknown</span><span class="p">,</span>
<span class="n">EntityNotFound</span><span class="p">,</span>
<span class="n">InvalidStatusChange</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-csharp 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">ErrorHandlingExtensions</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">ObjectResult</span> <span class="nf">CreateErrorResult</span><span class="p">(</span><span class="k">this</span> <span class="n">ActionContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">ApiErrorCode</span> <span class="n">errorCode</span><span class="p">,</span> <span class="kt">object</span> <span class="n">additionalDetails</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">problemDetailsFactory</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">.</span><span class="n">RequestServices</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p"><</span><span class="n">ProblemDetailsFactory</span><span class="p">>();</span>
<span class="kt">var</span> <span class="n">problemDetails</span> <span class="p">=</span> <span class="n">problemDetailsFactory</span><span class="p">.</span><span class="nf">CreateProblemDetails</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">,</span> <span class="n">statusCode</span><span class="p">:</span> <span class="p">(</span><span class="kt">int</span><span class="p">?)</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">BadRequest</span><span class="p">);</span>
<span class="n">problemDetails</span><span class="p">.</span><span class="n">Extensions</span><span class="p">[</span><span class="s">"code"</span><span class="p">]</span> <span class="p">=</span> <span class="n">errorCode</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">additionalDetails</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">problemDetails</span><span class="p">.</span><span class="n">Extensions</span><span class="p">[</span><span class="s">"additionalDetails"</span><span class="p">]</span> <span class="p">=</span> <span class="n">additionalDetails</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ObjectResult</span><span class="p">(</span><span class="n">problemDetails</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p><em>Disclaimer: In this example, the ASP.NET Core <code class="language-plaintext highlighter-rouge">ProblemDetails</code> is used but is not following all the best practices described in the <a href="https://tools.ietf.org/html/rfc7807">RFC</a> for the sake of simplicity.</em></p>
</blockquote>
<h2 id="registering-our-custom-mvcfilter">Registering our custom <code class="language-plaintext highlighter-rouge">Mvc.Filter</code></h2>
<p>For the final touch, we just need to register our filter in the <code class="language-plaintext highlighter-rouge">ConfigureServices</code> of our <code class="language-plaintext highlighter-rouge">Startup.cs</code> file:</p>
<div class="language-csharp 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">AddControllersWithViews</span><span class="p">().</span><span class="nf">AddMvcOptions</span><span class="p">(</span><span class="n">options</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">options</span><span class="p">.</span><span class="n">Filters</span><span class="p">.</span><span class="n">Add</span><span class="p"><</span><span class="n">MyAppErrorFilter</span><span class="p">>();</span>
<span class="p">})</span>
<span class="p">.</span><span class="nf">AddJsonOptions</span><span class="p">(</span><span class="n">options</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">options</span><span class="p">.</span><span class="n">JsonSerializerOptions</span><span class="p">.</span><span class="n">Converters</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">JsonStringEnumConverter</span><span class="p">(</span><span class="n">options</span><span class="p">.</span><span class="n">JsonSerializerOptions</span><span class="p">.</span><span class="n">PropertyNamingPolicy</span><span class="p">,</span> <span class="k">false</span><span class="p">));</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p><em>Note: For a better readability of our <code class="language-plaintext highlighter-rouge">ApiErrorCode</code> enum, we also added the <code class="language-plaintext highlighter-rouge">JsonStringEnumConverter</code> to use our enum through <code class="language-plaintext highlighter-rouge">string</code> values rather than <code class="language-plaintext highlighter-rouge">int</code></em></p>
</blockquote>
<h2 id="api-result">API result</h2>
<p>With all of this implemented, we should now get the following error result from our API:</p>
<p><img src="/assets/2020-11-25/02a-generic-error-with-payload.png" alt="02a-generic-error-with-payload" /></p>
<p>Alright with all of this setup, what we are left now is to see how we can handle these errors in our Front end.</p>
<h1 id="interacting-with-apis-and-intercepting-errors-using-axios">Interacting with APIs and intercepting errors using <code class="language-plaintext highlighter-rouge">axios</code></h1>
<p><code class="language-plaintext highlighter-rouge">axios</code> library is pretty common when it comes to handle API calls, and one thing that I personnally like is it’s <a href="https://github.com/axios/axios#interceptors">interceptors</a> implementation.</p>
<p>This will allow us to handle the errors returned by the API, and more specifically our generic errors.</p>
<p>The code necessary for this should be quite straightforward:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">axiosInstance</span> <span class="o">=</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
<span class="nx">axiosInstance</span><span class="p">.</span><span class="nx">interceptors</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">response</span><span class="p">:</span> <span class="nx">AxiosResponse</span><span class="o"><</span><span class="kr">any</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
<span class="p">},</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">problemDetails</span><span class="p">:</span> <span class="nx">ProblemDetails</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">problemDetails</span><span class="p">.</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="nx">ApiErrorCode</span><span class="p">.</span><span class="na">EntityNotFound</span><span class="p">:</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">EntityNotFound!</span><span class="dl">"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="nx">ApiErrorCode</span><span class="p">.</span><span class="na">InvalidStatusChange</span><span class="p">:</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`Invalid Status Change! Allowed status: </span><span class="p">${(</span><span class="nx">problemDetails</span><span class="p">.</span><span class="nx">additionalDetails</span><span class="p">.</span><span class="nx">allowedStatus</span> <span class="k">as</span> <span class="kr">string</span><span class="p">[]).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default</span><span class="p">:</span>
<span class="c1">// Do nothing and let specific custom business exception handling.</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>In this example, we add an <code class="language-plaintext highlighter-rouge">interceptor</code> to an <code class="language-plaintext highlighter-rouge">axios</code> instance, and provide the <code class="language-plaintext highlighter-rouge">error</code> handling part:</p>
<ul>
<li>We first retrieve our <code class="language-plaintext highlighter-rouge">ProblemDetails</code> from the error response</li>
<li>Then we lookup for the <code class="language-plaintext highlighter-rouge">code</code> property which gives us the type of error raised, and we can treat our generic error as we want (we display a toast notification in this example)</li>
<li>Notice on the <code class="language-plaintext highlighter-rouge">InvalidStatusChange</code> error that we can also customize our error handling by looking at our <code class="language-plaintext highlighter-rouge">additionalDetails</code> property which gives us more details and information to act properly</li>
<li>Finally, if the error is not one of our “generic errors”, we just do nothing and rethrow it to let our app handle it by itself (maybe for more custom non generic error, page specific, etc.)</li>
</ul>
<p>Also to make it easier to use, we could also wrap all of this configuration into a custom React hook such as:</p>
<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">AxiosContext</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">axiosInstance</span><span class="p">?:</span> <span class="nx">AxiosInstance</span> <span class="p">};</span>
<span class="kd">const</span> <span class="nx">initialContext</span><span class="p">:</span> <span class="nx">AxiosContext</span> <span class="o">=</span> <span class="p">{</span> <span class="na">axiosInstance</span><span class="p">:</span> <span class="kc">undefined</span> <span class="p">};</span>
<span class="kd">const</span> <span class="nx">AxiosReactContext</span> <span class="o">=</span> <span class="nx">createContext</span><span class="o"><</span><span class="nx">AxiosContext</span><span class="o">></span><span class="p">(</span><span class="nx">initialContext</span><span class="p">);</span>
<span class="c1">// 1 component to define an Axios Instance in a Context</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">AxiosProvider</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FunctionComponent</span><span class="o"><</span><span class="p">{</span> <span class="na">children</span><span class="p">:</span> <span class="nx">ReactNode</span> <span class="p">}</span><span class="o">></span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">contextValue</span><span class="p">:</span> <span class="nx">AxiosContext</span> <span class="o">=</span> <span class="nx">useMemo</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">axiosInstance</span> <span class="o">=</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">create</span><span class="p">();</span>
<span class="nx">axiosInstance</span><span class="p">.</span><span class="nx">interceptors</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span>
<span class="p">...</span> <span class="c1">// configure axios interceptor as above</span>
<span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="nx">axiosInstance</span> <span class="p">};</span>
<span class="p">},</span> <span class="p">[]);</span>
<span class="k">return</span> <span class="p">(<</span><span class="nc">AxiosReactContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">contextValue</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="si">}</span>
<span class="p"></</span><span class="nc">AxiosReactContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">>)</span>
<span class="p">}</span>
<span class="c1">// 1 custom hook to access the Axios Instance from the Context</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">useAxios</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">AxiosReactContext</span><span class="p">).</span><span class="nx">axiosInstance</span><span class="p">;</span>
</code></pre></div></div>
<p>In this example, I define a custom React hook in which I configure the interceptor as described above and expose it through the React <a href="https://reactjs.org/docs/context.html">Context API</a>.</p>
<p>Then we only need to define it in our <code class="language-plaintext highlighter-rouge">App.tsx</code> file as below :</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+import { AxiosProvider } from './custom-hooks/useAxios';
</span>
export default class App extends Component {
static displayName = App.name;
render() {
return (
<Layout>
<span class="gi">+ <AxiosProvider>
</span> <Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/status' component={Status} />
<Route path='/fetch-data' component={FetchData} />
<ToastContainer position="bottom-right" />
<span class="gi">+ </AxiosProvider>
</span> </Layout>
);
}
<span class="err">}</span>
</code></pre></div></div>
<p>And then use it anywhere in our app to call APIs like this:</p>
<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">axios</span> <span class="o">=</span> <span class="nx">useAxios</span><span class="p">();</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">axios</span><span class="p">)</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/weatherforecast/1</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="dl">"</span><span class="s2">result</span><span class="dl">"</span><span class="p">,</span> <span class="nx">result</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})()</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">axios</span><span class="p">]);</span>
</code></pre></div></div>
<p><img src="/assets/2020-11-25/02b-generic-error-with-payload.PNG" alt="02b-generic-error-with-payload" /></p>
<p>And Voilà! we can now call freely any API from our <code class="language-plaintext highlighter-rouge">React</code> app without worrying about handling any generic error which could occur :)</p>
<h1 id="in-conclusion">In conclusion</h1>
<p>That’s all for this article, I hope it could provide you with some idea if you were looking for a similar solution.
As it is a pretty common problem, I am expecting that you have encountered/implementer other systems. Feel free to share your solutions in the comments, and as usual you can reach me on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>.</p>
<h1 id="bonus-add-swagger-to-this-solution">Bonus: Add swagger to this solution</h1>
<p>To get a nice description of our generic error handling (<em>and especially of our <code class="language-plaintext highlighter-rouge">ProblemDetails</code> custom extensions</em>) in swagger like this:</p>
<p><img src="/assets/2020-11-25/03-generic-error-with-swagger-support.PNG" alt="03-generic-error-with-swagger-support" /></p>
<p>We need to add a custom <code class="language-plaintext highlighter-rouge">ISchemaFilter</code> to our swagger configuration as described in the following <code class="language-plaintext highlighter-rouge">commit</code>:<br />
https://github.com/vfabing/simple-aspnetcore-react-shared-generic-errors/commit/c5ab26dc9f6f0ad8e71d19100f5c1f3c5ffc52f4</p>
<p>You may ask why focus so much on getting a nice description in our swagger system, well all of these efforts could help “generate” automatically some code and be able to share very easily our models and API clients… But this is a subject for another blog article :)</p>
<p>Till then, May the code be with you!</p>Vivien FabingGiven an ASP.NET Core React app, sometimes you might want to throw some errors from any API endpoint, and be able to handle them gracefully in your Front end. For generic well known errors (401, 403, 404, etc.), the API part is pretty simple and a generic Front end handler system such as axios interceptors should do the job quite well.ASP.NET Core: Simple shared components with React2020-08-31T11:37:00+00:002020-08-31T11:37:00+00:00https://www.vivienfabing.com/react/2020/08/31/aspnetcore-simple-shared-components-with-react<p>There are a lot of approaches to sharing components between <code class="language-plaintext highlighter-rouge">React</code> projects (<a href="https://storybook.js.org/">StoryBook</a>, <a href="https://github.com/lerna/lerna">Lerna</a>, <a href="https://docs.microsoft.com/en-us/azure/devops/artifacts/get-started-npm">private npm registries</a>, etc.), but what if you just created 2 new <code class="language-plaintext highlighter-rouge">ASP.NET Core</code> app using <code class="language-plaintext highlighter-rouge">dotnet new react</code> (<em>let’s say Front Office app named<code class="language-plaintext highlighter-rouge">Front</code> and Back Office app called<code class="language-plaintext highlighter-rouge">Back</code></em>) and wanted a simple way to share <code class="language-plaintext highlighter-rouge">React</code> components between these 2 apps?</p>
<p>Well my friend you are on the right blog article, let’s see together how we can achieve this scenario using one of the simplest way of doing it!</p>
<blockquote>
<p>tl;dr: you can find a working example on this <a href="https://github.com/vfabing/aspnetcore-react-simple-component-sharing">github repository</a></p>
</blockquote>
<h2 id="workflow">Workflow</h2>
<p>I propose us to create a simple npm library (called <code class="language-plaintext highlighter-rouge">shared</code> in this example) with it’s own simple build workflow (to transform <code class="language-plaintext highlighter-rouge">Typescript</code> files into <code class="language-plaintext highlighter-rouge">JavaScript</code> files + <code class="language-plaintext highlighter-rouge">typings</code>, etc.), and then install our library inside of our <code class="language-plaintext highlighter-rouge">Front</code> and <code class="language-plaintext highlighter-rouge">Back</code> <code class="language-plaintext highlighter-rouge">React</code> apps.</p>
<p>Hard to make it simpler, isn’t it? But wait, there are few pitfalls so let’s now review together step by step how we could implement this simple workflow.</p>
<p>The apps organization used in this example will be the following:</p>
<pre><code class="language-cmd">+---Back
| \---ClientApp
| +---App.tsx
| +---package.json
| +---...
+---Front
| \---ClientApp
| +---App.tsx
| +---package.json
| +---...
+---shared
| \---src
| +---components
| | +---BlueButton.tsx
| +---sass
| | +---BlueButton.scss
| +---package.json
</code></pre>
<h2 id="initialize-our-shared-components-library">Initialize our <code class="language-plaintext highlighter-rouge">shared</code> components library</h2>
<p>Let’s create a new npm library which will generate in a <code class="language-plaintext highlighter-rouge">dist</code> folder <code class="language-plaintext highlighter-rouge">Javascript</code> and <code class="language-plaintext highlighter-rouge">typings</code> files from <code class="language-plaintext highlighter-rouge">Typescript</code> source code, as well as copy <code class="language-plaintext highlighter-rouge">SASS</code> files for styling.</p>
<p>Inside of your <code class="language-plaintext highlighter-rouge">shared</code> folder, run the <code class="language-plaintext highlighter-rouge">npm init</code> command to show the npm library creation wizard. You can keep all the default values and just give a name to your library (<em>such as <code class="language-plaintext highlighter-rouge">shared</code></em>). This should generate your <code class="language-plaintext highlighter-rouge">package.json</code> starting file</p>
<p>Then install <code class="language-plaintext highlighter-rouge">Typescript</code> (<em>if you want to use it</em>) as well as <code class="language-plaintext highlighter-rouge">React</code></p>
<pre><code class="language-cmd">npm i --save-dev typescript
npm i --save-dev @types/node
npm i --save-dev @types/react
npm i --save react
npm i --save react-dom
</code></pre>
<blockquote>
<p>Now the <strong>first very important step</strong> (<em>which was found by my dear colleague <a href="https://twitter.com/mikaelguillin">Mikaël</a></em>): Edit your <code class="language-plaintext highlighter-rouge">package.json</code> file and move <code class="language-plaintext highlighter-rouge">react</code> and <code class="language-plaintext highlighter-rouge">react-dom</code> from <code class="language-plaintext highlighter-rouge">dependencies</code> to <code class="language-plaintext highlighter-rouge">peerDependencies</code>.
This is a crucial step to do because if you don’t, our <code class="language-plaintext highlighter-rouge">shared</code> library will use it’s own <code class="language-plaintext highlighter-rouge">React</code> version and <a href="https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react">break</a> our <code class="language-plaintext highlighter-rouge">Front</code> and <code class="language-plaintext highlighter-rouge">Back</code> apps if you use <code class="language-plaintext highlighter-rouge">React hooks</code>…
You should have something looking like the following:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"peerDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"react"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^16.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"react-dom"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^16.0.0"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div> </div>
</blockquote>
<p>You will also need to add a <code class="language-plaintext highlighter-rouge">tsconfig.json</code> file such as:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"jsx"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react"</span><span class="p">,</span><span class="w">
</span><span class="nl">"declaration"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./dist/"</span><span class="p">,</span><span class="w">
</span><span class="nl">"esModuleInterop"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"moduleResolution"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node"</span><span class="p">,</span><span class="w">
</span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./src"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"include"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"src/**/*"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Now the <strong>second most important part</strong> (to me), is the scripts which will make all the package generation process smooth.
Modify the <code class="language-plaintext highlighter-rouge">package.json</code> file to add <code class="language-plaintext highlighter-rouge">copy-sass</code> (<em>to copy our SASS files</em>), <code class="language-plaintext highlighter-rouge">build-ts</code> (<em>to generate our <code class="language-plaintext highlighter-rouge">Javascript</code> and <code class="language-plaintext highlighter-rouge">typings</code> files</em>) and <code class="language-plaintext highlighter-rouge">build</code> (to call the <code class="language-plaintext highlighter-rouge">copy-sass</code> and <code class="language-plaintext highlighter-rouge">build-ts</code>) scripts:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"copy-sass"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(robocopy src dist *.scss /E) ^& IF %ERROR_LEVEL% LEQ 1 exit 0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build-ts"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tsc"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npm run copy-sass && npm run build-ts"</span><span class="p">,</span><span class="w">
</span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"echo </span><span class="se">\"</span><span class="s2">Error: no test specified</span><span class="se">\"</span><span class="s2"> && exit 1"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>
<p>Finally, you can add a component and a sass file, and generate your package by running <code class="language-plaintext highlighter-rouge">npm run build</code></p>
<h2 id="reference-our-shared-components-library">Reference our <code class="language-plaintext highlighter-rouge">shared</code> components library</h2>
<p>OK, be prepared for the most complicated part!!… Nah just kidding</p>
<p>Let’s make it in 3 simple steps:</p>
<p>Move to your ClientApp folder (from the <code class="language-plaintext highlighter-rouge">Front</code> or <code class="language-plaintext highlighter-rouge">Back</code> for instance), and run the <code class="language-plaintext highlighter-rouge">npm i PATH_TO_YOUR_SHARED_FOLDER</code> command line, such as:</p>
<pre><code class="language-cmd">CD Front\ClientApp
npm i ../../shared
</code></pre>
<h3 id="typescript">Typescript</h3>
<p>To use our component from our <code class="language-plaintext highlighter-rouge">Home.tsx</code> file for instance, we only need import it like this:</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">BlueButton</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">shared/dist/components/BlueButton</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div></div>
<p>Then add our component to the <code class="language-plaintext highlighter-rouge">render()</code> section:</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Home</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
...
<span class="p"><</span><span class="nc">BlueButton</span> <span class="p">/></span>
...
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>Note: referencing the compiled code version (<em>namely from the <code class="language-plaintext highlighter-rouge">dist</code> folder</em>) is required and should not reference the source code directly (<em>namely from the <code class="language-plaintext highlighter-rouge">src</code> folder</em>) as it is the scenario which quite not well supported from <code class="language-plaintext highlighter-rouge">React</code> apps created using <code class="language-plaintext highlighter-rouge">create-react-app</code>.</p>
</blockquote>
<h3 id="sass">SASS</h3>
<p>To use our SASS file, we can just reference it from our <code class="language-plaintext highlighter-rouge">App.scss</code> file like the following:</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@import</span> <span class="s2">"shared/dist/sass/BlueButton"</span><span class="p">;</span>
</code></pre></div></div>
<p>Et voilà! Our shared component is successfully installed!
<img src="/assets/2020-10-29/01-aspnetcore-react-shared-components-ui.PNG" alt="01-aspnetcore-react-shared-components-ui" /></p>
<h2 id="development-workflow">Development workflow</h2>
<p>With all of this is place, you need to remember that every time you modify something from the <code class="language-plaintext highlighter-rouge">shared</code> library, you will need to run the <code class="language-plaintext highlighter-rouge">npm run build</code> command to generate the compiled code.</p>
<p>The good news is that the app will reload automatically when you do it:</p>
<p><img src="/assets/2020-10-29/02-aspnetcore-react-shared-components-development-hot-reloading.gif" alt="02-aspnetcore-react-shared-components-development-hot-reloading" /></p>
<h2 id="in-conclusion">In conclusion</h2>
<p>And that’s the end of this small article about a simple system to share components between two <code class="language-plaintext highlighter-rouge">React</code> apps hosted on an <code class="language-plaintext highlighter-rouge">aspnetcore</code> app.</p>
<p>Obviously regarding the size of your project or if you want to share your components across much more applications, you would want to have a look to the other solutions I mentioned at the beginning of this article.</p>
<p>In any case, I hope this could help you in any way. Feel free to reach me on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a> or in the comments, and may the code be with you!</p>Vivien FabingThere are a lot of approaches to sharing components between React projects (StoryBook, Lerna, private npm registries, etc.), but what if you just created 2 new ASP.NET Core app using dotnet new react (let’s say Front Office app namedFront and Back Office app calledBack) and wanted a simple way to share React components between these 2 apps?Building a PWA with Blazor WebAssembly2020-07-28T11:37:00+00:002020-07-28T11:37:00+00:00https://www.vivienfabing.com/blazor/2020/07/28/blazor-building-a-pwa<p>Last month I had the wonderful opportunity to speak at the <a href="https://www.blazorday.net/">BlazorDay online conference</a> about <code class="language-plaintext highlighter-rouge">Building a PWA with Blazor WebAssembly</code> (<em>Replay is available on <a href="https://youtu.be/XoizucRjxgU?t=18130">Youtube</a></em>)</p>
<p>If you are more cumfortable with reading a small blog article in few minutes instead of watching a 30m video, stay with me and let’s me explain the content of the talk.</p>
<h2 id="workbox-or-not-workbox">Workbox or not Workbox</h2>
<blockquote>
<p><em><strong>tl;dr:</strong> you can say goodbye to Workbox :)</em></p>
</blockquote>
<p>You might have noticed (or not), but this article could be considered partly as an update of my previous blog article <a href="https://www.vivienfabing.com/blazor/2019/10/31/blazor-how-to-get-a-blazor-pwa-using-workbox.html">Blazor - Part 4: How to get a Blazor PWA using Workbox</a>.</p>
<p>At that time, there were no PWA support out-of-the box with Blazor, and manually setting up everything was not especially the easiest thing to do. So using an already battle-tested tool to simplify the transformation into a PWA seemed to me like the right thing to do.</p>
<p>But we are talking of something dated from a long time ago! (<em>Well around 8 months to be more precise haha</em>) and to date, <code class="language-plaintext highlighter-rouge">Blazor WebAssembly</code> is now officially released in the .NET Core SDK 3.1.300+.</p>
<p>At first, I really thought that the PWA support of Blazor would be something very simple, but would rapidly need to be replaced by a more robust toolchain such as <code class="language-plaintext highlighter-rouge">Workbox</code>…
But I chased this idea from my mind very quickly I discovered the great work done by the <code class="language-plaintext highlighter-rouge">Blazor</code> team on this subject!</p>
<p>But still, as I think this subject is not always the simplest (<em>especially the <code class="language-plaintext highlighter-rouge">service-worker</code> lifecycle part</em>), let’s see the magic under <code class="language-plaintext highlighter-rouge">dotnet new blazorwasm -o MyNewProject --pwa</code> (<em>or behind the <code class="language-plaintext highlighter-rouge">Progressive Web Application</code> checkbox in Visual Studio</em>)</p>
<h2 id="make-your-app-installable">Make your app installable</h2>
<p>To make your app installable, nothing extraordinary on this side.
You can have a look on what is added to your Blazor app to make it installable in this <a href="https://github.com/vfabing/presentation-2020-06-BlazorDay/commit/2055af5c708f9d980678fd9d5a994cd32c8cb4ba">Github commit</a></p>
<p>Don’t forget also to register a service worker, even if empty, to be able to be considered as a PWA in <a href="https://developers.google.com/web/tools/lighthouse">lighthouse</a>.</p>
<blockquote>
<p><em>For more information about all available configuration possible, have a look to the official documention on https://developer.mozilla.org/en-US/docs/Web/Manifest.</em></p>
</blockquote>
<h2 id="add-offline-support-using-the-service-workerjs">Add offline support using the service-worker.js</h2>
<p>Ok things are getting interesting starting from this one.
For offline support, you need basically 2 things:
1) A list of all the statics assets to be cached in the browser.
2) A description of how you want to handle your cache usage and update.</p>
<p>For the first one, <code class="language-plaintext highlighter-rouge">Blazor</code> provides us with an MSBuild property named <code class="language-plaintext highlighter-rouge"><ServiceWorkerAssetsManifest></code> which will take care of listing all the files in the published output, produce a <code class="language-plaintext highlighter-rouge">hash</code> to know if the file has changed, and resume all of this information in a <code class="language-plaintext highlighter-rouge">service-worker-assets.js</code> file. (<em>More info in this <a href="https://github.com/vfabing/presentation-2020-06-BlazorDay/commit/17bb7a1aa0d4a59e2acb90d6281ef6751fa93b77">Github commit</a></em>)</p>
<p>For the second one, <code class="language-plaintext highlighter-rouge">Blazor</code> provides us with an additional <code class="language-plaintext highlighter-rouge">service-worker.published.js</code> file containing implementing a <code class="language-plaintext highlighter-rouge">Cache, falling back to network</code> strategy.</p>
<p><a href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#cache-falling-back-to-network"><img src="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/images/ss-falling-back-to-network.png" alt="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#cache-falling-back-to-network" /></a>
<em>*Cache, falling back to network schema from developers.google.com/web/fundamentals</em></p>
<p>Though, as described in the <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/progressive-web-app?view=aspnetcore-3.1&tabs=visual-studio#offline-support">official documentation</a>, the offline support is only enabled for <em>published</em> apps, which means that the original <code class="language-plaintext highlighter-rouge">service-worker.js</code> file is replaced by the <code class="language-plaintext highlighter-rouge">service-worker.published.js</code> only when using the <code class="language-plaintext highlighter-rouge">dotnet publish</code> command (<em>or when publishing through Visual Studio</em>)</p>
<p>Alright, if you’ve come that far, congratulation, your application is offline enabled.
But as usual, as I don’t like black box, let’s see a little bit more what is done inside.</p>
<h2 id="service-worker-lifecycle-events">Service worker lifecycle events</h2>
<p>If you have a look inside of <code class="language-plaintext highlighter-rouge">service-worker.published.js</code> file, you will see a simple workflow made of 4 parts:</p>
<ul>
<li>A <code class="language-plaintext highlighter-rouge">self.assetsManifest</code> reference to your assets manifest, containing the list of the file needed to be cached, as well as a version computed from the hashes</li>
<li>an action when <code class="language-plaintext highlighter-rouge">installing</code> your service worker</li>
<li>an action when <code class="language-plaintext highlighter-rouge">activating</code> it</li>
<li>and lastly an action when <code class="language-plaintext highlighter-rouge">fetching</code> resources.</li>
</ul>
<p>To start with, the <code class="language-plaintext highlighter-rouge">installing</code> event will be triggered when a new version of your PWA assets is published, and will cache all of your assets in a cache named from your assets manifest version.
When the installation process is completed, your service worker new version will be in the <code class="language-plaintext highlighter-rouge">waiting</code> state, waiting for the user to close all instances of your PWA to <code class="language-plaintext highlighter-rouge">activate</code> your new version.</p>
<p>During the <code class="language-plaintext highlighter-rouge">activation</code> process, the <em>new</em> service worker version becomes the <em>current</em> version, and all other version are deleted as they are not used anymore.</p>
<p>Finally, everytime the user request a resource (image, icons, html file, js files, etc.), the <code class="language-plaintext highlighter-rouge">fetch</code> event is triggered and will look into the cache if the resource is available, and if yes, will return it, without even triggering any network request.</p>
<p>Well, the best might still be to debug the service worker by yourself and see step by step how this each events are triggered.</p>
<h2 id="to-go-further">To go further</h2>
<p>The service worker <code class="language-plaintext highlighter-rouge">update</code> workflow is sometime quite surprising, and you might wonder how could we <code class="language-plaintext highlighter-rouge">notify</code> the user that a new version is available, and give him an <code class="language-plaintext highlighter-rouge">update now</code> button. Well you can find an <a href="https://github.com/vfabing/presentation-2020-06-BlazorDay/commit/ed874f4ea688913faa0e29f1a8523f0e6818a392">example on my Github</a> of how to do that by using the <code class="language-plaintext highlighter-rouge">self.skipWaiting()</code> instruction, as well the <code class="language-plaintext highlighter-rouge">BroadcastChannel</code> API to communicate between your service worker and your web app.</p>
<p><img src="/assets/2020-07-28/01-blazor-progressive-web-app-update-now-button.png" alt="01-blazor-progressive-web-app-update-now-button.png" /></p>
<p>That being said, I hope this article could be a good refresh if you didn’t remember clearly my public speaking, or helped you understand this subject better if you prefer reading than watching!</p>
<p>As usual, feel free to reach me out on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a> or anywhere else, and may the code be with you!</p>Vivien FabingLast month I had the wonderful opportunity to speak at the BlazorDay online conference about Building a PWA with Blazor WebAssembly (Replay is available on Youtube)Azure Boards practical example - Getting a realistic Sprint scope of work2020-06-29T11:37:00+00:002020-06-29T11:37:00+00:00https://www.vivienfabing.com/azure-devops/2020/06/29/azure-boards-practical-example-during-sprint-planning<p>After <a href="https://www.vivienfabing.com/azure-devops/2020/04/30/azureboards-practical-example-before-sprint-planning.html">preparing the Sprint Backlog</a>, as well as preparing the <a href="https://www.vivienfabing.com/azure-devops/2020/05/31/azure-boards-practical-example-before-sprint-planning-capacity.html">Sprint Capacity</a>, let’s now see how we can define a realistic scope of work, according to our Team capacity (that is to say, let’s see now how we can get an initial <code class="language-plaintext highlighter-rouge">Remaining Work</code> coherent with the Team <code class="language-plaintext highlighter-rouge">capacity</code> for the Sprint)</p>
<p>This objective should usually be the main focus during the second half of the Sprint Planning, when there is enough <code class="language-plaintext highlighter-rouge">Remaining Work</code> defined to identify potential bottlenecks (<em>Too much work for Front developers, Too much work for a particular member, etc.</em>)</p>
<blockquote>
<p><em>Disclaimer: These articles are very personal so feel free to disagree. I am hoping to provide a interesting feedback to help you make your own decisions.</em></p>
</blockquote>
<h2 id="personal-feedback-how-do-i-do-estimates-with-azure-boards-tasks">Personal feedback: How do I do estimates with Azure Boards <code class="language-plaintext highlighter-rouge">Tasks</code></h2>
<p>First of all, I would like to share a personal feedback about <code class="language-plaintext highlighter-rouge">how I scale my estimates</code>.</p>
<p>The first question I ask is not so much “How much time should it take to do this” but rather “How much time would you need to be comfortable realizing this properly”.
I realized that for the first wording, I received very often <strong>very tight estimates</strong>, sometimes conceiving a <strong>quick and dirty</strong> solution as the developers feel they should give the <strong>smallest estimate possible to please the product owner</strong> (<em>again this is not always true, but I did see this very often</em>).
For that reason, I prefer to use the second wording, challenging the developer if needed to fit these estimates with a tight schedule, but by default, I would keep the estimate of a realistic and clean implementation.
This approach is also aligned with my vision of planification with estimates: <strong>It is usually easier to deal with initial estimates too big than too small :)</strong></p>
<p>For the scale used, originally I liked to use the <a href="https://en.wikipedia.org/wiki/Fibonacci_number">Fibonacci sequence</a> <strong>to represent how innacurrate estimates become when the scope is getting bigger</strong>. However as I discovered this was not so mainstream as I thought and now use simpler estimates as following:</p>
<ul>
<li>1 hour*</li>
<li>2 hours</li>
<li>half a day (3 hours)</li>
<li>one day (6 hours)</li>
</ul>
<p><em>By default, I don’t have any task which would take less than 1 hour (because it implies understanding, developing, testing, deploying and communicating). If really too easy, It could be either be regrouped with other tasks, or counted as a <strong>0 hour task</strong> (explanation following below)</em></p>
<p>I find that 90% of our estimates fit into these values (<em>using task splitting or regrouping when it makes sense</em>). For the 10% left, we sometimes use the <strong>0 hour task</strong> (or “free” task) when the task is really something really trivial, as well as the <code class="language-plaintext highlighter-rouge">in between half a day and one day</code> or even <code class="language-plaintext highlighter-rouge">one day and half</code> and <code class="language-plaintext highlighter-rouge">2 days</code> for really complex tasks such as architecturing the solution or <code class="language-plaintext highlighter-rouge">PoC</code> / <code class="language-plaintext highlighter-rouge">Spikes</code> tasks. But again, this is <strong>not</strong> most of our estimates (<em>and very important that they stay like this</em>)</p>
<p>This kind of discussion could continue very long, but let’s go back to <code class="language-plaintext highlighter-rouge">Azure Boards</code> configuration.</p>
<h2 id="choose-your-tracking-strategy-for-your-non-development-work-planned-or-unplanned">Choose your tracking strategy for your non-development work, planned or unplanned</h2>
<p>For every Sprint, you will have few recurring workload, for which you have to decide how do you want to take them into account, while not impacting the way you follow your Sprint advancement. Namely:</p>
<ol>
<li>Sprint Planning</li>
<li>Sprint Demo + its preparation</li>
<li>Sprint Retrospective</li>
<li><code class="language-plaintext highlighter-rouge">User Stories technical review</code> before Sprint planning (<em>As described in the <a href="https://www.vivienfabing.com/azure-devops/2020/04/30/azureboards-practical-example-before-sprint-planning.html">first article</a> of this serie</em>).</li>
<li>Bug fixing (aouch. This one could take its own blog post haha)</li>
<li>etc.</li>
</ol>
<p>For point 1 to 4, we chose to fix some values (<em>~half a day for Sprint Planning, 2 hours for the Sprint Demo, 1 hour for the Retrospective, between half a day and an entire day for the technical review</em>), so for each member participating to these, I usually create a corresponding task per member in the Sprint Planning.</p>
<p><img src="/assets/2020-06-29/01-azure-boards-sprint-planning-recurring-tasks.png" alt="01-azure-boards-sprint-planning-recurring-tasks.png" /></p>
<p>Thanks to these little recurring tasks, team members will be able to spend some time freely on these important tasks, without worrying of putting the Sprint Burndown in danger or not!</p>
<blockquote>
<p><em>You can import all of these recurring task at once using <a href="https://docs.microsoft.com/en-us/azure/devops/boards/backlogs/office/bulk-add-modify-work-items-excel">Excel</a></em></p>
</blockquote>
<p>Regarding the <code class="language-plaintext highlighter-rouge">bug fixing</code>, this is a widely and heavily debated subject (<em>as you can see if you try to look for <code class="language-plaintext highlighter-rouge">estimating bugs</code> in your favourite search engine</em>).</p>
<p>I liked some answers provided by Dan Makarov in its blog post <a href="https://hackernoon.com/should-you-estimate-bugs-4ocf37t2">How Should You Estimate Bugs?</a>, and I invite to try these during few Sprints to see if it could fit into the way your team is working.
All in all, this is mainly a cursor to adjust between <code class="language-plaintext highlighter-rouge">being totally blind</code> about bug fixing estimation, and <code class="language-plaintext highlighter-rouge">spending too much time/effort</code> trying to estimate them :)</p>
<p>Personally I am quite fan of the “time booking” (<em>around half a day up to 1 day</em>) per member for each Sprint, allowing to absorb most of the bug fixing.
In the end, I still think it is a matter of your team preferences and depends heavily on how you are organizing yourselves (<em>Feel free to reach me for an opened discussion on the subject on <a href="https://twitter.com/vivienfabing">Twitter</a> or in the comments haha</em>)</p>
<h2 id="azure-boards---get-a-realistic-scope-for-your-sprint">Azure Boards - get a realistic scope for your Sprint</h2>
<p>This is the last part! If you’ve made it so far, congratulations! Just a little bit more efforts, and you should get a nice start for your Sprint!</p>
<p>What’s left is very simple:</p>
<ul>
<li>Have a look to the current sprint <code class="language-plaintext highlighter-rouge">Work details</code>, and make sure there are no “red bars”, and you are good to go!</li>
</ul>
<p><img src="/assets/2020-06-29/02-azure-boards-sprint-planning-work-details.png" alt="02-azure-boards-sprint-planning-work-details.png" /></p>
<p>And for this, Azure Boards provide you with 3 different way to make sure that the scope your team will commit to deliver is reachable:</p>
<ul>
<li>First you get the overall <code class="language-plaintext highlighter-rouge">Work</code>: overall <code class="language-plaintext highlighter-rouge">capacity</code> of your team vs. overall <code class="language-plaintext highlighter-rouge">Remaining Work</code> in the Sprint.</li>
<li>Second, if you made the effort to set the <code class="language-plaintext highlighter-rouge">Activity</code> field of work items and Team members, you get the <code class="language-plaintext highlighter-rouge">Work By: Activity</code> metrics: With this you get a more detailed vision regarding fields (<em><code class="language-plaintext highlighter-rouge">C#</code> and <code class="language-plaintext highlighter-rouge">JS</code> in the screenshot for instance</em>) and can detect earlier than certain fields have already too much work planned, even if the overall scope seemed fine.
<blockquote>
<p><em>In the example screenshot, some of your full stack developer will have to handle a little bit more of <code class="language-plaintext highlighter-rouge">JS</code> tasks, and not ony <code class="language-plaintext highlighter-rouge">C#</code> ones. If you don’t have full stack developers, you might want to reduce the scope involving <code class="language-plaintext highlighter-rouge">JS</code> workload</em></p>
</blockquote>
</li>
<li>Lastly, you get the <code class="language-plaintext highlighter-rouge">Work By: Assigned To</code> metric: Usually, I don’t really want to use this one, because in my ideal organization, there is not such a thing as <code class="language-plaintext highlighter-rouge">only him/her can do this</code> kind of task, and team members adapt and choose the task they want to implement on a day to day basis.<br />
However in reality, it happens… And we already know from the beginning who is going to implement the tasks… So in that case, we assign the tasks to the team member so that at least, if too much work is assigned to him, well you get the “red bar” and know that either he/she will need to stop sleeping, or you will need to reduce workload involving him/her :)</li>
</ul>
<p>This work details part is really useful. You can also know when you could stop your sprint planning when you see that the overall <code class="language-plaintext highlighter-rouge">Work</code> bar is full!</p>
<p>And when it is, it is probably time to wish everyone good luck, and start focusing for your new Sprint!</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Alright, that’s all for this <code class="language-plaintext highlighter-rouge">Sprint Planning</code> part. I hope that you could discover something new of confirm practices you were already using (or feel strongly in a disagreement with me, and you might want to reach me on my Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>?)</p>
<p>All in all, I wish you the best, and May the code be with you!</p>Vivien FabingAfter preparing the Sprint Backlog, as well as preparing the Sprint Capacity, let’s now see how we can define a realistic scope of work, according to our Team capacity (that is to say, let’s see now how we can get an initial Remaining Work coherent with the Team capacity for the Sprint)Azure Boards practical example - Sprint Capacity2020-05-31T11:37:00+00:002020-05-31T11:37:00+00:00https://www.vivienfabing.com/azure-devops/2020/05/31/azure-boards-practical-example-before-sprint-planning-capacity<p>After <a href="https://www.vivienfabing.com/azure-devops/2020/04/30/azureboards-practical-example-before-sprint-planning.html">preparing as best as we could our Sprint Planning to keep it short and efficient</a>, let’s see now how we can get a realistic Team <code class="language-plaintext highlighter-rouge">capacity</code> for the Sprint.</p>
<blockquote>
<p><em>Disclaimer: These articles are very personal so feel free to disagree. I am hoping to provide a interesting feedback to help you make your own decisions.</em></p>
</blockquote>
<h2 id="sprint-capacity-preparation"><strong>Sprint Capacity</strong> preparation</h2>
<p>To calculate the <code class="language-plaintext highlighter-rouge">Sprint capacity</code>, <code class="language-plaintext highlighter-rouge">Azure Boards</code> will mainly try to count the number of <code class="language-plaintext highlighter-rouge">Working days</code> in a Sprint, and will multiply it by the <code class="language-plaintext highlighter-rouge">daily capacity</code> in <code class="language-plaintext highlighter-rouge">Hours</code> of the <code class="language-plaintext highlighter-rouge">Team members</code>.</p>
<p>So let’s first see how to configure the <code class="language-plaintext highlighter-rouge">Working days</code> of a Sprint.</p>
<h2 id="configure-the-sprint-working-days">Configure the Sprint <strong>Working days</strong></h2>
<p>First of all, you have to set the <a href="https://docs.microsoft.com/en-us/azure/devops/organizations/settings/set-working-days?view=azure-devops&tabs=preview-page#configure-working-days">Team Working Days</a> if not already done. (<em>Note that by default only <code class="language-plaintext highlighter-rouge">Saturday</code> and <code class="language-plaintext highlighter-rouge">Sunday</code> are not considered as working days</em>).</p>
<p><img src="/assets/2020-05-31/01a-azure-boards-sprint-working-days-setup.png" alt="01a-azure-boards-sprint-working-days-setup.png" /></p>
<p>Then obviously, we need to <a href="https://docs.microsoft.com/en-us/azure/devops/organizations/settings/set-iteration-paths-sprints?view=azure-devops&tabs=preview-page#add-iterations-and-set-iteration-dates">set the Sprint Dates</a></p>
<p><img src="/assets/2020-05-31/01b-azure-boards-sprint-dates.png" alt="01b-azure-boards-sprint-dates.png" /></p>
<p>Finally, we need to make sure that all Team members are defined in the <a href="https://docs.microsoft.com/en-us/azure/devops/boards/sprints/set-capacity?view=azure-devops#set-capacity-for-the-team-and-team-members">Sprint capacity</a>, and then define the <code class="language-plaintext highlighter-rouge">Team days off</code> (if any).</p>
<p><img src="/assets/2020-05-31/01c-azure-boards-sprint-days-off.png" alt="01c-azure-boards-sprint-days-off.png" /></p>
<p>From there you can already check the calculated Sprint working days<br />
<img src="/assets/2020-05-31/01-azure-boards-sprint-working-days.png" alt="01-azure-boards-sprint-working-days.png" /></p>
<p>If possible, don’t forget also to specify individual team members days off (also if any of course).</p>
<p>Alright, so far we manipulated <code class="language-plaintext highlighter-rouge">days</code>, but most of <code class="language-plaintext highlighter-rouge">Azure Boards</code> features are expressed in <code class="language-plaintext highlighter-rouge">hours</code>, so let’s see how to convert these <code class="language-plaintext highlighter-rouge">days</code> into <code class="language-plaintext highlighter-rouge">hours</code>.</p>
<h2 id="personal-feedback-use-1-day--6-hours-to-convert-your-estimates">Personal feedback: Use <strong>1 day = 6 hours</strong> to convert your estimates</h2>
<p>As <code class="language-plaintext highlighter-rouge">Azure DevOps</code> only use hours for estimates, we use <code class="language-plaintext highlighter-rouge">6 hours</code> when we want to describe an estimate of a <code class="language-plaintext highlighter-rouge">full day task</code> and <code class="language-plaintext highlighter-rouge">3 hours</code> for a <code class="language-plaintext highlighter-rouge">half a day task</code>.</p>
<p><code class="language-plaintext highlighter-rouge">1 day</code> corresponding to <code class="language-plaintext highlighter-rouge">6 hours</code> is a completely arbitrary value and was chosen as the most fitting value for us, because it enables us to still get meaningful estimates while not <code class="language-plaintext highlighter-rouge">micro tracking</code> every unplanned events (<em>pair programming, review, questions, discussions etc. etc.</em>)</p>
<h2 id="define-sprint-global-capacity-and-capacity-by-activity-and-member">Define <strong>Sprint global capacity</strong> and capacity by <strong>activity</strong> and <strong>member</strong></h2>
<p>Given the estimates scale described before, let’s first configure the <a href="https://docs.microsoft.com/en-us/azure/devops/boards/sprints/set-capacity?view=azure-devops#set-capacity-for-the-team-and-team-members">Team members capacity</a>, specifying a capacity of <code class="language-plaintext highlighter-rouge">6</code> hours (for a full day of work) per team member.</p>
<p><img src="/assets/2020-05-31/02b-azure-boards-sprint-members-capacity.png" alt="02b-azure-boards-sprint-members-capacity.png" /></p>
<p>With this, you should be able to see the <code class="language-plaintext highlighter-rouge">Sprint global capacity</code> as well as the individual <code class="language-plaintext highlighter-rouge">member capacity</code> from selecting <code class="language-plaintext highlighter-rouge">Work details</code> in the <code class="language-plaintext highlighter-rouge">View options</code>:</p>
<p><img src="/assets/2020-05-31/02-azure-boards-sprint-global-and-members-work-capacity.png" alt="02-azure-boards-sprint-global-and-members-work-capacity.png" /><br />
<em>In the picture above, the <code class="language-plaintext highlighter-rouge">Team overall capacity</code> is <code class="language-plaintext highlighter-rouge">600 hours</code> (meaning 10 people in the team for a 10 days Sprint), and <code class="language-plaintext highlighter-rouge">12 hours</code> of <code class="language-plaintext highlighter-rouge">Remaining Work</code> are already defined in the Sprint.<br />
For the Team member <code class="language-plaintext highlighter-rouge">Vivien</code>, he has a capacity of <code class="language-plaintext highlighter-rouge">60 hours</code>, and is already assigned to some <code class="language-plaintext highlighter-rouge">Tasks</code>, for a overall of <code class="language-plaintext highlighter-rouge">12 hours</code> of <code class="language-plaintext highlighter-rouge">Remaining Work</code> to do during the Sprint.</em></p>
<blockquote>
<p><em>You can typically see this kind of view before or at the beginning of the <code class="language-plaintext highlighter-rouge">Sprint Planning</code></em></p>
</blockquote>
<p>That’s a good start and could already be sufficiant for your context, but I found myself using very often another feature: The Task <code class="language-plaintext highlighter-rouge">Activity</code> field.</p>
<p>This field enables us to categorize <code class="language-plaintext highlighter-rouge">tasks</code> by affinity:<br />
For instance, I often see teams composed of <code class="language-plaintext highlighter-rouge">JavaScript/TypeScript</code> developers and <code class="language-plaintext highlighter-rouge">C#</code> developers. So instead of just seeing <code class="language-plaintext highlighter-rouge">600 hours of team capacity</code>, would not it be better to get a little bit more detailed view and be able to see <code class="language-plaintext highlighter-rouge">180 hours of JavaScript/TypeScript capacity</code>, <code class="language-plaintext highlighter-rouge">300 hours of C# capacity</code> and <code class="language-plaintext highlighter-rouge">120 hours of organization</code>?</p>
<p><img src="/assets/2020-05-31/03-azure-boards-sprint-activity-capacity.png" alt="03-azure-boards-sprint-activity-capacity.png" /></p>
<blockquote>
<p><em>Note that by default, Activity field contains only <code class="language-plaintext highlighter-rouge">Deployment</code>, <code class="language-plaintext highlighter-rouge">Design</code>, <code class="language-plaintext highlighter-rouge">Development</code>, <code class="language-plaintext highlighter-rouge">Documentation</code>, <code class="language-plaintext highlighter-rouge">Requirements</code> and <code class="language-plaintext highlighter-rouge">Testing</code> values. If you want to use custom values as shown above, you need to <a href="https://stackoverflow.com/a/48659130">customize the <code class="language-plaintext highlighter-rouge">Activity</code> field</a>.<br />
If you don’t have access to the work item customization, you can still agree on a defined mapping inside the team such as <code class="language-plaintext highlighter-rouge">Design</code> for <code class="language-plaintext highlighter-rouge">JavaScript</code> developers, <code class="language-plaintext highlighter-rouge">Development</code> for <code class="language-plaintext highlighter-rouge">C#</code> developers, <code class="language-plaintext highlighter-rouge">Requirement</code> for the team members related to organization, etc.</em></p>
</blockquote>
<p>For this, you will need to come back once again to the <a href="https://docs.microsoft.com/en-us/azure/devops/boards/sprints/set-capacity?view=azure-devops#set-capacity-for-the-team-and-team-members">Sprint Capacity</a> tab and configure this time the <code class="language-plaintext highlighter-rouge">Activity</code> of the Team member (<em>and add multiple activities if the Team members needs to perform different type of tasks</em>)</p>
<p><img src="/assets/2020-05-31/03b-azure-boards-sprint-member-activity.png" alt="03b-azure-boards-sprint-member-activity.png" /></p>
<h2 id="wrap-up">Wrap up</h2>
<p>That’s all for this second part about <code class="language-plaintext highlighter-rouge">Sprint capacity</code> preparation with Azure DevOps.</p>
<p>I hope this article could give you an overview of <code class="language-plaintext highlighter-rouge">capacity management</code> with Azure DevOps. Of course, this article is heavily related to the next article which will talk about <code class="language-plaintext highlighter-rouge">getting a realistic scope of work</code> for a Sprint. (<em>I will try to write it as soon as possible not to let the suspens fade away haha</em>)</p>
<p>Feel free to show your disagreements or any other opinion in the comments or in reply to my Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>.
May the code be with you!</p>Vivien FabingAfter preparing as best as we could our Sprint Planning to keep it short and efficient, let’s see now how we can get a realistic Team capacity for the Sprint.Azure Boards practical example - Before the Sprint Planning2020-04-30T11:37:00+00:002020-04-30T11:37:00+00:00https://www.vivienfabing.com/azure-devops/2020/04/30/azureboards-practical-example-before-sprint-planning<blockquote>
<p>We want to do this <strong>project</strong> given this <strong>budget</strong>, and we want to do it in <strong>Scrum/Agile</strong>!</p>
</blockquote>
<p>If you are familiar with this sentence, you are in the right place and are welcome on the following series of blog articles trying to explain how the <code class="language-plaintext highlighter-rouge">Azure Boards</code> module from <code class="language-plaintext highlighter-rouge">Azure DevOps</code> can help us achieve this.</p>
<blockquote>
<p><em>Disclaimer: These articles are very personal so feel free to disagree. I am hoping to provide a interesting feedback to help you make your own decisions.</em></p>
</blockquote>
<h2 id="quick-reminder-why-do-i-want-a-sprint-planning">Quick reminder: Why do I want a Sprint Planning</h2>
<p>I consider the <code class="language-plaintext highlighter-rouge">Sprint Planning</code> definitely as one of the most important ceremonial of the <code class="language-plaintext highlighter-rouge">Scrum</code>, and among all the benefits it can provide, my favourites are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">As a developer, I can concentrate on being productive</code>. We are supposed to have made sure that the US (User Stories) are crystal clear, and listed all the necessary tasks to complete the US.</li>
<li><code class="language-plaintext highlighter-rouge">As a product owner, I have more time to focus</code> on preparing future US rather making sure than everything is well understood by the team everyday.</li>
<li>The initial scope + the task estimates enable us to get a daily vision of our performance, as well as being able to give <code class="language-plaintext highlighter-rouge">more transparency to other people</code>.</li>
</ul>
<p>That’s being said, if everybody is convinced of its necessity, it does imply that it can be executed randomly.
There are many pitfall, and duration is a very dangerous one.</p>
<h2 id="keeping-the-sprint-planning-short-and-efficient-requires-preparation">Keeping the Sprint planning short and efficient requires preparation</h2>
<blockquote>
<ul>
<li>Let’s go for another full day of Sprint Planning! Cheers everyone!</li>
<li>…</li>
</ul>
</blockquote>
<p>Alright, meetings are time consuming.<br />
And <strong>*spoiler alert*</strong> the <code class="language-plaintext highlighter-rouge">Sprint Planning</code> is like any other meeting:<br />
If you don’t prepare for it, it will be long and unefficient (<em>and will probably kill any good will coming from your teammates</em>).</p>
<p>I prefer having the team spending 4 days preparing for the next Sprint and be able to do a full Sprint Planning in half a day, rather than spending a whole day with the whole team, wasting (teammates number * 1 day) days + not getting a clear vision of the Sprint backlog.</p>
<p>I am also annoyed when a new User Story is explained and we either loose (<em>who said waste?</em>) a lot of time debating how to implement it, or either create a <code class="language-plaintext highlighter-rouge">Spike</code> or <code class="language-plaintext highlighter-rouge">PoC</code> and move the completion of the US for the next Sprint.<br />
To be more precise, if I was working on a project where <code class="language-plaintext highlighter-rouge">Time</code> (<em>often tightly coupled with <code class="language-plaintext highlighter-rouge">Cost</code></em>) was not a problem, I would prefer these 2 solutions, but unfortunately I didn’t encouter too much of these kind of projects :)</p>
<p>And I could continue longer (<em><a href="https://twitter.com/vivienfabing">tweet me</a> if you want to continue</em>), but let’s move on to the <code class="language-plaintext highlighter-rouge">How</code> we can try to improve the duration and efficiency of the Sprint Planning.</p>
<h2 id="my-ideal-user-stories">My ideal User Stories</h2>
<p>Yes I personally think that even in Agile, we need more than just a <code class="language-plaintext highlighter-rouge">sentence on a post it</code> to be able to estimates correctly a functionality. (<em>more details about this in a previous blog post: <a href="https://www.vivienfabing.com/azure-devops/2019/02/06/do-i-still-need-traditional-specifications-when-using-user-stories-practical-usage-in-azure-boards.html">Do I still need traditional specifications when using User Stories?</a></em>)</p>
<p>But a Sprint backlog is not a random list of business rules either, so let me share how I like them to be written:</p>
<p><img src="/assets/2020-04-30/02-my-ideal-user-story.png" alt="02-my-ideal-user-story.png" /></p>
<ol>
<li>I prefer a concise <code class="language-plaintext highlighter-rouge">Title</code>, with keywords easy to remember that the team will be able to reuse to communicate. I also like to add some [Tags] to see easily to which part this US is from.</li>
<li><code class="language-plaintext highlighter-rouge">Acceptance Criteria</code> is where I am the strictest. I use the classical <code class="language-plaintext highlighter-rouge">As a</code>, <code class="language-plaintext highlighter-rouge">I want</code> <code class="language-plaintext highlighter-rouge">In order to</code> to understand who, what and why. (<em>And I do insist on the <code class="language-plaintext highlighter-rouge">why ?</code> to enable to think about the big picture and not just do because we were asked to…</em>). If needed, we can also add some small impact business rules precisions in this field also.</li>
<li>In the <code class="language-plaintext highlighter-rouge">Description</code> I usually want the links to the design as well as any other technical precision / direction.</li>
<li>Very important, if I consider that the User Story is not ready, I add a <code class="language-plaintext highlighter-rouge">Comment</code> mentionning the proper teammate as well as putting him in the <code class="language-plaintext highlighter-rouge">Assigned to</code> field.</li>
<li>It might be very specific to our organization, but we like to know to which skills the US is related to (<em>Front-End Development, Back-End, Design, Project Management, etc.</em>) in order to be able to do some optimization (e.g.: <em>next sprint is heavily related to the <code class="language-plaintext highlighter-rouge">Back-End</code> so we need to think about what <code class="language-plaintext highlighter-rouge">Front-End</code> dev can do!</em>)</li>
<li>As an IT consulting company, we need to provide workload estimations using days (<em>I could not find so far any project where we could sell Story Points workload :D</em>), so I usually put it in the <code class="language-plaintext highlighter-rouge">Story Point</code> or <code class="language-plaintext highlighter-rouge">Effort</code> field in order to keep track easily of the initial estimation and see how good or bad it was :)</li>
</ol>
<h2 id="how-do-we-organize-to-write-them">How do we organize to write them?</h2>
<p>Getting a finalized backlog, ready for the Sprint Planning is a Team effort. Here is my favourite process in order to get it ready using the minimum time possible:</p>
<ol>
<li>The PO (Product owner) write them, especially the <code class="language-plaintext highlighter-rouge">Title</code>, the <code class="language-plaintext highlighter-rouge">Acceptance Criteria</code> with Business rules, the <code class="language-plaintext highlighter-rouge">Description</code> with the Design (<em>if applicable</em>). If the project we are working on was estimated ahead, we also set the <code class="language-plaintext highlighter-rouge">Story Point</code> or <code class="language-plaintext highlighter-rouge">Effort</code> with the estimated workload in days.
<ul>
<li>This is clearly time consuming. Main problem being that ideally up to 2 Sprints of User Stories should be ready in order to keep the Team active. Taking 1 or 2 days per Sprint to write them properly does not surprise me that much.</li>
</ul>
</li>
<li>Then some technical fellow (<em>ideally experienced ones</em>) read them, and check if everything is clear and ready for splitting and estimating.
<ul>
<li>If not, he had some questions + mentions in the <code class="language-plaintext highlighter-rouge">Comments</code>, and <code class="language-plaintext highlighter-rouge">Assigned to</code> the mentioned teammate (<em>PO or designer for instance</em>).</li>
<li>He also add some <code class="language-plaintext highlighter-rouge">Tags</code> to know which skill will be needed.</li>
<li>If everything is ok, he also create the technical <code class="language-plaintext highlighter-rouge">Tasks</code> needed to complete the User Story and add an <code class="language-plaintext highlighter-rouge">Original Estimate</code></li>
<li>Reviewing everything is also time consuming, especially if many comments exchanges are needed. 1~1.5 day per Sprint dedicated to this task looks pretty fine to me.</li>
</ul>
</li>
</ol>
<blockquote>
<p><em>Note: Creating the <code class="language-plaintext highlighter-rouge">Tasks</code> and estimating them in advance is highly debatable. However by experience, I observed that very often if we don’t do this, this is usually the experienced developer who ends up doing it during the Sprint Planning, making the whole team “waste” some precious time I would prefer them spending explaining what they conceived and how to implement it.</em></p>
</blockquote>
<p>This <strong>second part</strong> is where this whole process shine according to me:</p>
<ul>
<li>It makes sure that the User Story is ready to be developped (as at least one developer could conceive how to do it)</li>
<li>It gives time to perform quick background tasks:
<ul>
<li>Get an answer from the customer</li>
<li>Get additional designs</li>
<li>Look for the best way to resolve the problems required by the User Story by asking feedback from other colleagues internally, etc.</li>
</ul>
</li>
</ul>
<h2 id="wrap-up">Wrap up</h2>
<p>That’s all for this first part about <code class="language-plaintext highlighter-rouge">Sprint Planning</code> preparation with Azure DevOps.</p>
<p>I hope it could give you some new ways of conceiving how your team can work in Agile (or reassure the way you are doing it)</p>
<p>Next time, I will discuss about how to obtain a realistic capacity with Azure DevOps.</p>
<p>Feel free to show your disagreements or any other opinion in the comments or in reply to my Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>.
May the code be with you!</p>Vivien FabingWe want to do this project given this budget, and we want to do it in Scrum/Agile!How to authenticate easily an SPA with Azure AD in an aspnetcore app2020-03-06T11:37:00+00:002020-03-06T11:37:00+00:00https://www.vivienfabing.com/aspnetcore/2020/03/06/how-to-authenticate-easily-an-spa-with-azure-ad-in-an-aspnetcore-app<p>Few months ago, my dear colleague <a href="https://blogs.infinitesquare.com/users/jantoine">Jonathan</a> gave us a presentation to the various way of connecting an SPA to an aspnetcore API using <a href="https://blogs.infinitesquare.com/posts/web/open-id-connect-et-oauth-les-differents-flow-de-connexion">OpenId Connect</a>, and he said something which kept stuck in my mind :</p>
<blockquote>
<p>At the end of the day, <code class="language-plaintext highlighter-rouge">Cookie authentication</code> is probably the most secure way of protecting your API.</p>
</blockquote>
<p>Lately, I had to add some simple email checking to secure an <code class="language-plaintext highlighter-rouge">aspnetcore API</code> using <code class="language-plaintext highlighter-rouge">Azure AD</code> for a <code class="language-plaintext highlighter-rouge">React</code> SPA, and I tried to look for some examples on <code class="language-plaintext highlighter-rouge">Microsoft Docs</code> but could only find what I would qualify of “overkill” (<em>showing examples using <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-flows-app-scenarios#single-page-public-client-and-confidential-client-applications">MSAL.js</a>, etc.</em>)</p>
<h2 id="how-we-came-up-with-this-simple-authentication-workflow">How we came up with this simple authentication workflow</h2>
<p>So I called to the rescue my other good colleague <a href="https://blogs.infinitesquare.com/users/touvre">Thomas</a> and he first guided me toward the default <code class="language-plaintext highlighter-rouge">Azure AD</code> integration in an ASP.NET Core web apps (<em>that you can obtain easily following the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp">official documentation</a></em>).
He told me to look especially at the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp#startup-class"><code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Authentication</code> middleware</a> which by default was securing all <code class="language-plaintext highlighter-rouge">Controllers</code> endpoints, and was redirecting the users to the Azure AD authentication page if they were not already authenticated.</p>
<p>But there was few <strong>problems</strong> with this:</p>
<ul>
<li>When the <code class="language-plaintext highlighter-rouge">React</code> SPA was calling the API, they would get a redirection <code class="language-plaintext highlighter-rouge">302</code> instead of a <code class="language-plaintext highlighter-rouge">401</code> unauthorized HTTP status code.</li>
<li>Authentication would only check that the user was successfully authenticated on the associated Azure AD tenant, but would not check if the user was authorized to access to the app (<em>i.e. his email was present in the database</em>)</li>
</ul>
<p>So with the help of <a href="https://blogs.infinitesquare.com/users/touvre">Thomas</a>, we somehow managed to find a “simple” <strong>workflow</strong> authenticate our SPA users:</p>
<ul>
<li>When user try to access to an API without being authenticated, return a <code class="language-plaintext highlighter-rouge">401</code> error.</li>
<li>When getting an <code class="language-plaintext highlighter-rouge">401</code> unauthorized, redirect the user to the <code class="language-plaintext highlighter-rouge">/api/auth</code> endpoint.</li>
<li>The endpoint redirect the user to the <code class="language-plaintext highlighter-rouge">Azure AD</code> login page and call back the <code class="language-plaintext highlighter-rouge">/api/auth</code> endpoint after, with an <code class="language-plaintext highlighter-rouge">Authentication Cookie</code>.</li>
<li>When successfully authenticated, the <code class="language-plaintext highlighter-rouge">/api/auth</code> redirect the user to the main page</li>
<li>Additional API calls are made with the <code class="language-plaintext highlighter-rouge">Authentication Cookie</code> and are successful.</li>
</ul>
<p>And to achieve this, we needed to add <strong>some modifications</strong> to the default Azure AD Authentication as following:</p>
<ol>
<li>Instead of redirecting the users to the Azure AD Login page when calling an API without being authenticated, return them a <code class="language-plaintext highlighter-rouge">401</code> status code.</li>
<li>Configure the <code class="language-plaintext highlighter-rouge">Azure AD Authentication</code> to be made using <code class="language-plaintext highlighter-rouge">Cookies</code> (so that [Authorize] protected endpoints can check if the cookie is present or not).</li>
<li>In the SPA, call the <code class="language-plaintext highlighter-rouge">/api/auth</code> path to authenticate yourself on <code class="language-plaintext highlighter-rouge">Azure AD</code></li>
<li>In the same API endpoint, check the user email, and show an error message if not authorized.</li>
</ol>
<h2 id="override-default-redirection-to-azure-ad-to-return-a-401-unauthorized-status-code">Override default redirection to Azure AD to return a <strong>401</strong> Unauthorized Status Code</h2>
<p>In order to do this, we need to define the <code class="language-plaintext highlighter-rouge">DefaultChallengeScheme</code> as our <code class="language-plaintext highlighter-rouge">CustomApiScheme</code>, as well as creating and registering our own <code class="language-plaintext highlighter-rouge">AuthenticationHandler</code> to return a <code class="language-plaintext highlighter-rouge">401</code> status code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">CustomApiScheme</span> <span class="p">=</span> <span class="s">"CustomApiScheme"</span><span class="p">;</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">ConfigureAuthentication</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">AddAuthentication</span><span class="p">(</span><span class="n">options</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">options</span><span class="p">.</span><span class="n">DefaultAuthenticateScheme</span> <span class="p">=</span> <span class="n">AzureADDefaults</span><span class="p">.</span><span class="n">CookieScheme</span><span class="p">;</span>
<span class="n">options</span><span class="p">.</span><span class="n">DefaultChallengeScheme</span> <span class="p">=</span> <span class="n">CustomApiScheme</span><span class="p">;</span>
<span class="n">options</span><span class="p">.</span><span class="n">DefaultSignInScheme</span> <span class="p">=</span> <span class="n">AzureADDefaults</span><span class="p">.</span><span class="n">CookieScheme</span><span class="p">;</span>
<span class="p">})</span>
<span class="p">.</span><span class="nf">AddAzureAD</span><span class="p">(</span><span class="n">options</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">Configuration</span><span class="p">.</span><span class="nf">Bind</span><span class="p">(</span><span class="s">"AzureAd"</span><span class="p">,</span> <span class="n">options</span><span class="p">);</span>
<span class="p">})</span>
<span class="p">.</span><span class="n">AddScheme</span><span class="p"><</span><span class="n">AuthenticationSchemeOptions</span><span class="p">,</span> <span class="n">CustomApiAuthenticationHandler</span><span class="p">>(</span><span class="n">CustomApiScheme</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="p">}</span>
</code></pre></div></div>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CustomApiAuthenticationHandler</span> <span class="p">:</span> <span class="n">AuthenticationHandler</span><span class="p"><</span><span class="n">AuthenticationSchemeOptions</span><span class="p">></span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">CustomApiAuthenticationHandler</span><span class="p">(</span><span class="n">IOptionsMonitor</span><span class="p"><</span><span class="n">AuthenticationSchemeOptions</span><span class="p">></span> <span class="n">options</span><span class="p">,</span> <span class="n">ILoggerFactory</span> <span class="n">logger</span><span class="p">,</span> <span class="n">UrlEncoder</span> <span class="n">encoder</span><span class="p">,</span> <span class="n">ISystemClock</span> <span class="n">clock</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="n">logger</span><span class="p">,</span> <span class="n">encoder</span><span class="p">,</span> <span class="n">clock</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">HandleChallengeAsync</span><span class="p">(</span><span class="n">AuthenticationProperties</span> <span class="n">properties</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Response</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">=</span> <span class="m">401</span><span class="p">;</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="p"><</span><span class="n">AuthenticateResult</span><span class="p">></span> <span class="nf">HandleAuthenticateAsync</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="nf">FromResult</span><span class="p">(</span><span class="n">AuthenticateResult</span><span class="p">.</span><span class="nf">NoResult</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="use-cookies-for-the-azure-ad-authentication">Use <code class="language-plaintext highlighter-rouge">Cookies</code> for the Azure AD Authentication</h2>
<p>Nothing fancy here, just configure the <code class="language-plaintext highlighter-rouge">CookieAuthenticationOptions</code> using the <code class="language-plaintext highlighter-rouge">AzureADDefaults.CookieScheme</code> and define the properties you want your cookie to have.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">ConfigureAuthentication</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="p">...</span>
<span class="n">services</span><span class="p">.</span><span class="n">Configure</span><span class="p"><</span><span class="n">CookieAuthenticationOptions</span><span class="p">>(</span><span class="n">AzureADDefaults</span><span class="p">.</span><span class="n">CookieScheme</span><span class="p">,</span> <span class="n">options</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">options</span><span class="p">.</span><span class="n">Cookie</span><span class="p">.</span><span class="n">HttpOnly</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">options</span><span class="p">.</span><span class="n">Cookie</span><span class="p">.</span><span class="n">SecurePolicy</span> <span class="p">=</span> <span class="n">CookieSecurePolicy</span><span class="p">.</span><span class="n">Always</span><span class="p">;</span>
<span class="n">options</span><span class="p">.</span><span class="n">Cookie</span><span class="p">.</span><span class="n">SameSite</span> <span class="p">=</span> <span class="n">SameSiteMode</span><span class="p">.</span><span class="n">Lax</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">...</span>
</code></pre></div></div>
<p><img src="/assets/2020-03-06/01-aspnetcore-azuread-authentication-cookie.png" alt="01-aspnetcore-azuread-authentication-cookie.png" /></p>
<h2 id="redirect-to-the-apiauth-authentication-endpoint-when-receiving-a-401-unauthorized">Redirect to the <strong>/api/auth</strong> authentication endpoint when receiving a <strong>401</strong> unauthorized</h2>
<p>In our sample app, we only have one API call so I just made a small change in the <code class="language-plaintext highlighter-rouge">FetchData.js</code> file. In a more complex application, you probably would have to configure the behaviour you want in the <code class="language-plaintext highlighter-rouge">fetch</code>, <code class="language-plaintext highlighter-rouge">axios</code> or whatsoever way of getting data.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="nx">populateWeatherData</span><span class="p">()</span> <span class="p">{</span>
<span class="p">...</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">status</span> <span class="o">==</span> <span class="mi">401</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Redirect to authentication point if not authorized</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">/api/auth</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="trigger-the-azure-ad-authentication-in-your-authentication-endpoint">Trigger the Azure AD authentication in your authentication endpoint</h2>
<p>For this last part, you will need to have an <strong>anonymously</strong> accessible endpoint and would need to trigger the <code class="language-plaintext highlighter-rouge">Challenge(AzureADDefaults.OpenIdScheme)</code> when the user is not authenticated.
This should redirect the user to the Azure AD Login page, and should call back this endpoint with an <code class="language-plaintext highlighter-rouge">Authentication Cookie</code> when successfully authenticated.
You could then redirect the user to your SPA.</p>
<div class="language-csharp 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">"api/[controller]"</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">AuthController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
<span class="p">{</span>
<span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span>
<span class="p">[</span><span class="n">AllowAnonymous</span><span class="p">]</span>
<span class="k">public</span> <span class="n">IActionResult</span> <span class="nf">Login</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">User</span><span class="p">.</span><span class="n">Identity</span><span class="p">.</span><span class="n">IsAuthenticated</span><span class="p">)</span> <span class="c1">// = Is User authenticated by Azure AD</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="c1">// Check if user access is legitimate on this website, and throw UnauthorizedAccessException if not</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">UnauthorizedAccessException</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nf">Unauthorized</span><span class="p">(</span><span class="k">new</span> <span class="p">{</span> <span class="n">Message</span> <span class="p">=</span> <span class="s">"You are not authorized to access to this platform."</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="c1">// Trigger Azure AD authentication (using redirection)</span>
<span class="k">return</span> <span class="nf">Challenge</span><span class="p">(</span><span class="n">AzureADDefaults</span><span class="p">.</span><span class="n">OpenIdScheme</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Redirect to home page is successfully authenticated</span>
<span class="k">return</span> <span class="nf">Redirect</span><span class="p">(</span><span class="s">"/"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="additional-comments">Additional comments</h2>
<p>I was quite surprised, and believe my surprise was shared with many of my colleagues, regarding the lack of easy access to some Official Documentation explaining this workflow, which seems to me as one of the simplest way to secure access between an SPA and its API.</p>
<p>One important thing to note though is that this workflow requires the SPA to be served on the same domain as the API (<em>Which is great as the <code class="language-plaintext highlighter-rouge">dotnet new react</code> or <code class="language-plaintext highlighter-rouge">dotnet new angular</code> templates are already following this kind of architecture</em>), as <a href="https://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_path">Cookies are scoped by <code class="language-plaintext highlighter-rouge">Domain</code> and <code class="language-plaintext highlighter-rouge">Path</code></a>.</p>
<p>Finally, I hope this article could help you or at least let you discover another option to secure your API with Azure AD.</p>
<p>Feel free to show your disagreements or any other opinion in the comments or in reply to my Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>.
May the code be with you!</p>
<p>You can find a working sample on my <a href="https://github.com/vfabing/simple-aspnetcore-azuread-react">GitHub</a>.</p>Vivien FabingFew months ago, my dear colleague Jonathan gave us a presentation to the various way of connecting an SPA to an aspnetcore API using OpenId Connect, and he said something which kept stuck in my mind : At the end of the day, Cookie authentication is probably the most secure way of protecting your API.Azure Pipelines: How to add a build agent with docker-machine2020-01-30T11:37:00+00:002020-01-30T11:37:00+00:00https://www.vivienfabing.com/azure-devops/2020/01/30/azure-pipelines-how-to-add-a-build-agent-with-docker-machine<p>Last year, I wrote a small series of blog post about getting an azure pipelines agent in minutes using <code class="language-plaintext highlighter-rouge">Azure Container Instances</code> (<a href="https://www.vivienfabing.com/azure-devops/2019/05/14/azure-pipelines-how-to-add-a-build-agent-with-azure-container-instances.html">here</a>, <a href="https://www.vivienfabing.com/azure-devops/2019/06/20/azure-pipelines-how-to-add-a-build-agent-with-azure-container-instances-part-2-custom-agent.html">here</a> and <a href="https://www.vivienfabing.com/azure-devops/2019/08/22/azure-pipelines-how-to-add-a-build-agent-with-azure-container-instances-part-3-build-agent-on-demand.html">here</a>), and I got a great question from Thierry which made me admit that my favorite build environment is still having a self-hosted agent on an <code class="language-plaintext highlighter-rouge">Azure Virtual Machine</code>.</p>
<p>In this blog post, I would like to explain what are the advantages I find in this solution, and how to set it up in a matter of minutes.</p>
<h2 id="why-i-find-self-hosted-agent-on-an-azure-vm-often-better-than-microsoft-hosted-agent">Why I find self-hosted agent on an Azure VM often better than Microsoft-hosted agent</h2>
<blockquote>
<p>tl;dr: You get a faster and easier to debug build system.</p>
</blockquote>
<ol>
<li>When my colleagues tell me “My build takes too much time, how can I make it faster?”, very often my first answer is “What about using a self-hosted build machine?”.<br />
The reason for this is very simple: <code class="language-plaintext highlighter-rouge">Microsoft-hosted agents</code> are created and destroyed for each build, which means you can hardly reuse any out-of-the-box cache system (<em>Even though using the latest <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops">Pipelines caching</a> system could help, even with <a href="https://github.com/fadnavistanmay/azure-pipelines-caching-yaml">Docker</a></em>).<br />
With your own build machine, you can easily make faster:
<ul>
<li>Your <code class="language-plaintext highlighter-rouge">source code retrieval</code>: instead of doing a full <code class="language-plaintext highlighter-rouge">git clone</code>, you just do a light <code class="language-plaintext highlighter-rouge">git pull</code> like any developer would do.</li>
<li>Your <code class="language-plaintext highlighter-rouge">build time</code>: instead of doing a <code class="language-plaintext highlighter-rouge">Rebuild</code> of every project, you just do a <code class="language-plaintext highlighter-rouge">Build</code> on the project modified and the project dependents on them.</li>
<li>Your <code class="language-plaintext highlighter-rouge">package restoration time</code>: instead of downloading all of them everytime, you can first rely on your build machine <code class="language-plaintext highlighter-rouge">global package cache</code> (<em>and just copy the packages instead of downloading them</em>), but also just reuse the package from the previous build, and just add/update the new packages modified.</li>
<li>Your <code class="language-plaintext highlighter-rouge">docker build time</code>: You can benefit from the <code class="language-plaintext highlighter-rouge">images cache</code>, as well as the <code class="language-plaintext highlighter-rouge">docker build step cache</code> (<em>explained briefly on my article about <a href="https://www.vivienfabing.com/docker/2019/09/16/docker-optimize-aspnetcore-spa-container-with-visual-studio.html#docker-optimization-for-react-spa-application">optimizing an SPA docker build</a></em>)</li>
</ul>
</li>
<li>“The build crashed! Call a ALM/DevOps expert to fix it!”. I heard this sentence quite a lot in the past. To me, the build machine is here to <strong>automatize</strong> tasks which are usually done <strong>manually</strong> by developers. And in this regard, I expect the build machine to be like a developer machine as in <code class="language-plaintext highlighter-rouge">with the same tools, IDE, dependencies, etc.</code> installed, to make sure that if the build crashes, fixing it could be as simple as connecting to the build machine, launching Visual Studio or running the command line executed during the build, and debugging it. At the end of the day, having access to a <code class="language-plaintext highlighter-rouge">developer like build environment</code> makes fixing a self-hosted build is sometime easier imho.</li>
<li>Last point less important, and very dependent to your organization, but as <code class="language-plaintext highlighter-rouge">Microsoft-hosted</code> agents are usually used by default for every new pipelines, I often find their queues too busy and need to wait a few builds/releases to complete every time I need a build/release…<br />
So if you are lucky enough to have a few self-hosted pipelines at your disposal <em>(such as having many <code class="language-plaintext highlighter-rouge">Visual Studio Enterprise</code> users in your organization, giving you a free self-hosted pipeline per VS Enterprise user, or by buying them for 15$ each</em>), setting up your own self-hosted agent allows you to get your own <code class="language-plaintext highlighter-rouge">agent pool</code>, thus your independent <code class="language-plaintext highlighter-rouge">agent queue</code>, and stop waiting for Microsoft-hosted pipelines to finish.</li>
</ol>
<blockquote>
<p>Note: Don’t get me wrong, I still find <code class="language-plaintext highlighter-rouge">Microsoft-hosted agents</code> better for starters, and for rarely executed builds, or for which build time is not a problem. I even consider building your own build machine as an <code class="language-plaintext highlighter-rouge">advanced</code> scenario, so if you are not familiar with all of this, please stick with the <code class="language-plaintext highlighter-rouge">Microsoft-hosted agents</code> which are still very practical.</p>
</blockquote>
<blockquote>
<p><em>Disclaimer: Most of the ideas discussed above are interesting conceptually, but could be a little bit different from the reality as the Azure DevOps Teams does a great work to optimize all of these pain points. I have no doubt there are few hidden systems which make the experience I described not as bad as it is in real.</em></p>
</blockquote>
<p>Alright, convinced now? Let’s see how to setup your docker build in minutes then!</p>
<h2 id="how-to-setup-up-a-docker-build-machine-in-minutes">How to setup up a docker build machine in minutes</h2>
<p>2 years ago, I wrote <a href="https://blogs.infinitesquare.com/posts/alm/docker-sur-azure-setup-une-plateforme-de-dev-entiere-pour-pas-cher">this blog post</a> (<em>in French</em>) about creating your own docker build machine in Azure. Happens that I now use a even easier/faster way of doing it using the <code class="language-plaintext highlighter-rouge">docker-machine</code> command line.</p>
<blockquote>
<p>Warning: <code class="language-plaintext highlighter-rouge">Easier and faster</code> is not equal to <code class="language-plaintext highlighter-rouge">perfect</code> from the start (in a matter of maintainability, security, etc.). You will still need to grab some understanding on the underlying concepts (ssh, docker, Azure, etc.), just you will get a working environment faster that you can improve gradually :)</p>
</blockquote>
<p>But without further wait, here is the script I use to setup my azure docker build machine:</p>
<pre><code class="language-cmd">@ECHO OFF
SET PREFIX=myapp
SET ENV=build
SET LOC=WestEurope
SET AZURE_SUBSCRIPTION=7c6bed95-1337-1664-abcd-aa4691816e72
SET RG=%PREFIX%-rg-%ENV%
SET VM_NAME=%PREFIX%-dockermachine-%ENV%
SET AZUREVM_SIZE=Standard_D4s_v3
SET ADMIN_USER=myadmin
REM Create docker build machine with docker-machine command line (Installed with Docker Desktop)
CALL docker-machine create --driver azure --azure-subscription-id %AZURE_SUBSCRIPTION% --azure-resource-group %RG% --azure-location %LOC% --azure-ssh-user %ADMIN_USER% --azure-size "%AZUREVM_SIZE%" %VM_NAME%
REM Show environment variables to set to connect to the Docker service installed in the docker-machine
CALL docker-machine env %VM_NAME%
</code></pre>
<p>Hopefully the comments are self explanatory.
The last command <a href="https://docs.docker.com/v17.09/machine/reference/env/">docker-machine env</a> should show the environment variables to set to connect directly from your local computer to the docker service available on your docker build machine.</p>
<p>So after getting your machine running, you now need to run an <code class="language-plaintext highlighter-rouge">azure pipeline agent</code> on your VM.<br />
Of course you could install it directly in your VM by following the standard procedure <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-linux?view=azure-devops">Self-hosted Linux agents</a>, but you would then need to “pollute” your VM with dev dependencies, handling tools version conflict, etc.</p>
<p>As we are using <code class="language-plaintext highlighter-rouge">Docker</code>, a far better way (<em>to my opinion</em>) would be to run our build agent itself inside of a container!</p>
<p>We could achieve that by connecting to the docker service of our build machine, and then run the following script:</p>
<pre><code class="language-cmd">@ECHO OFF
SET PREFIX=myapp
SET ENV=build
SET VM_NAME=%PREFIX%-dockermachine-%ENV%
SET AZP_URL=https://dev.azure.com/vfabing/
SET AZP_TOKEN=7wzr66gqzjq42kjsjvdpyhl3zhello7dh3fhbopimdpxkkmyaigq
SET AZP_POOL=%PREFIX%-pool
SET AZP_AGENT_NAME=%VM_NAME%-01
SET AZP_AGENT_DOCKER_IMAGE=vfabing/azure-pipelines-agent-dotnet-core-sdk
REM Start an Azure Pipelines agent in a docker container
docker run -d --restart=always -e AZP_URL=%AZP_URL% -e AZP_TOKEN=%AZP_TOKEN% -e AZP_POOL=%AZP_POOL% -e AZP_AGENT_NAME=%AZP_AGENT_NAME% %AZP_AGENT_DOCKER_IMAGE%
</code></pre>
<blockquote>
<p><em>Note: If you want more information about creating your own azure pipelines docker agent image, or what are the parameters used, you can have a look to my previous blog article <a href="https://www.vivienfabing.com/azure-devops/2019/06/20/azure-pipelines-how-to-add-a-build-agent-with-azure-container-instances-part-2-custom-agent.html">Azure Pipelines: How to add a build agent with Azure Container Instances - part 2 : Custom Agent</a></em></p>
</blockquote>
<p>Et voilà! :)</p>
<blockquote>
<p>Note 2: If you want your build agent to be able to build docker images from Azure Pipelines, you will need to add 2 parameters to your <code class="language-plaintext highlighter-rouge">docker run</code> command line which are <code class="language-plaintext highlighter-rouge">-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker</code>.
However this has serious security implications as mentionned by <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker?view=azure-devops#using-docker-within-a-docker-container">Microsoft Docs</a> so be aware of that.</p>
</blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>I hope this article gave you more insight about azure devops docker build machines.
Feel free to react in the comments or on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>, and may the code be with you!</p>Vivien FabingLast year, I wrote a small series of blog post about getting an azure pipelines agent in minutes using Azure Container Instances (here, here and here), and I got a great question from Thierry which made me admit that my favorite build environment is still having a self-hosted agent on an Azure Virtual Machine.Kubernetes: Get a new environment for each Pull Request using Review Apps2019-12-03T11:37:00+00:002019-12-03T11:37:00+00:00https://www.vivienfabing.com/kubernetes/2019/12/03/kubernetes-get-new-environment-per-pull-request-using-review-apps<p>Hi everyone,</p>
<p>So like my you have heard about the so called <code class="language-plaintext highlighter-rouge">Review Apps</code> new feature in <code class="language-plaintext highlighter-rouge">Azure DevOps</code> using <code class="language-plaintext highlighter-rouge">Kubernetes</code> and you wanted to know more about this is working on some more concrete example?</p>
<p>Well welcome to this article which will show an example of how to test this new feature currently in public preview.</p>
<h2 id="workflow-of-review-apps">Workflow of Review Apps</h2>
<p>In short, the only thing you really need to do is create a new YAML Pipelines from Azure DevOps using the <code class="language-plaintext highlighter-rouge">Deploy to Kubernetes</code> template and using the <code class="language-plaintext highlighter-rouge">Enable Review Apps workflow for Pull Request</code> checkbox, and that’s all!
In addition your <code class="language-plaintext highlighter-rouge">Kubernetes</code> cluster will be registered as an Azure DevOps environment to get a user friendly way to access the Pull Request temporary environment.</p>
<p>What will happen is that for every Pull Request, the pipeline will be triggered and will create a new <code class="language-plaintext highlighter-rouge">Kubernetes</code> namespace to deploy inside.</p>
<p><img src="/assets/2019-12-03/01-review-apps-workflow.png" alt="01-review-apps-workflow.png" /></p>
<p>But enough explanation, let’s see a concrete example of Review Apps with a simple aspnetcore app.</p>
<h2 id="review-apps-prerequisites">Review Apps prerequisites</h2>
<p>In prerequisite, we need an app to build (<em>an aspnetcore app created with <code class="language-plaintext highlighter-rouge">dotnet new mvc</code> for instance</em>), as well as a <code class="language-plaintext highlighter-rouge">Dockerfile</code> to generate our container image to test.</p>
<blockquote>
<p>Note: To add docker support, you can use Visual Studio to generate a start Dockerfile as describe in the <a href="https://docs.microsoft.com/en-us/visualstudio/containers/overview?view=vs-2019#adding-docker-support">official documentation</a></p>
</blockquote>
<p>You will also need a <code class="language-plaintext highlighter-rouge">Kubernetes</code> cluster, such as an <code class="language-plaintext highlighter-rouge">Azure Kubernetes Service</code> and a container registry such as <code class="language-plaintext highlighter-rouge">Azure Container Registry</code>.</p>
<blockquote>
<p>Note: Microsoft also have some nice documentation about how to create an <a href="https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal#create-an-aks-cluster">AKS</a> from the Azure Portal GUI and how to create an <a href="https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal#create-a-container-registry">ACR</a></p>
</blockquote>
<h2 id="create-new-azure-devops-pipeline-to-deploy-from-kubernetes-and-activate-review-apps">Create new Azure DevOps Pipeline to deploy from Kubernetes and activate Review Apps</h2>
<p>The easiest way to start using <code class="language-plaintext highlighter-rouge">Review Apps</code> is to create a new Azure Pipelines and select the <code class="language-plaintext highlighter-rouge">Deploy to Azure Kubernetes Service</code> template.</p>
<p><img src="/assets/2019-12-03/02-create-deploy-to-azure-kubernetes-service-pipeline.png" alt="02-create-deploy-to-azure-kubernetes-service-pipeline.png" /></p>
<p>After selecting your <code class="language-plaintext highlighter-rouge">Kubernetes</code> cluster, you then just need to configure few things (<code class="language-plaintext highlighter-rouge">Namespace</code>, <code class="language-plaintext highlighter-rouge">Container registry</code>, the docker <code class="language-plaintext highlighter-rouge">Image Name</code>, the <code class="language-plaintext highlighter-rouge">Service Port</code> to access your environment), and more importantly, you need to check the <code class="language-plaintext highlighter-rouge">Enable Review App flow for Pull Requests</code> option.</p>
<p><img src="/assets/2019-12-03/03-enable-review-apps-flow-for-pull-request.png" alt="03-enable-review-apps-flow-for-pull-request.pngO" /></p>
<p>This will add 3 files:</p>
<ul>
<li>An <code class="language-plaintext highlighter-rouge">azure-pipelines.yml</code> file which defines your pipeline (<em>Build, Deploy and Deploy Pull Request</em>)</li>
<li>A <code class="language-plaintext highlighter-rouge">manifests/deployment.yml</code>file which describe your app to deploy on <code class="language-plaintext highlighter-rouge">Kubernetes</code> (<em>docker image name and port to use</em>)</li>
<li>A <code class="language-plaintext highlighter-rouge">manifests/service.yml</code> file which describe your <code class="language-plaintext highlighter-rouge">Kubernetes</code> Service, and especially how to access it (<em>which port to use, and the <code class="language-plaintext highlighter-rouge">LoadBalancer</code> meaning it should obtain an public IP provided by the Azure Cloud provider</em>)</li>
</ul>
<p>If you have a look at the <code class="language-plaintext highlighter-rouge">azure-pipelines.yaml</code> content, you will differentiate 3 stages to be executed:</p>
<ul>
<li>The <code class="language-plaintext highlighter-rouge">Build</code> stage will just generate our docker images using the <code class="language-plaintext highlighter-rouge">docker build</code> command on our Dockerfile</li>
<li>The <code class="language-plaintext highlighter-rouge">Deploy</code> stage is to be executed when the branch build is just a normal branch, and will deploy a <code class="language-plaintext highlighter-rouge">Kubernetes</code> service on the configured namespace</li>
<li>The <code class="language-plaintext highlighter-rouge">DeployPullRequest</code> stage will be triggered when the source branch is a Pull Request branch, and will create a new <code class="language-plaintext highlighter-rouge">Kubernetes</code> namespace and deploy the service in it.</li>
</ul>
<h2 id="trigger-our-build-automatically-for-each-pull-request-using-branch-policies">Trigger our build automatically for each Pull Request using Branch Policies</h2>
<p>Obviously we need the build to be triggered automatically when a new Pull Request is created, so for that we just need to add or new build to the Branch Policies
of our common branch (<code class="language-plaintext highlighter-rouge">master</code>for instance).</p>
<p>You can see how to configure this on the <a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops#build-validation">official documentation</a>.</p>
<h2 id="fixing-error-a-valid-name-is-less-than-256-characters-in-length-and-does-not-contain-the-following-characters">Fixing error “A valid name is less than 256 characters in length and does not contain the following characters”</h2>
<p>If you try to create a new Pull Request now, you might see it fail because of a small bug in the <code class="language-plaintext highlighter-rouge">Kubernetes</code> namespace created, using by default the <code class="language-plaintext highlighter-rouge">Source Branch Name</code>, which is containing invalid characters for a <code class="language-plaintext highlighter-rouge">Kubernetes</code> namespace.</p>
<p><code class="language-plaintext highlighter-rouge">##[error]Resource name 'refs/heads/MY_PR_NAME' is not valid. A valid name is less than 256 characters in length and does not contain the following characters: ',', '"', '/', '\', '[', ']', ':', '|', '<', '>', '+', '=', ';', '?', and '*'.</code></p>
<p>I decided to replace it by my build name suffixed by the Pull Request ID which gives us:
<code class="language-plaintext highlighter-rouge">k8sNamespaceForPR: 'reviewappsdemo-$(System.PullRequest.PullRequestId)'</code></p>
<p>Trigger again a new build for a Pull Request, and enjoy seeing it finally succeeding! Congratulations!</p>
<h2 id="get-the-kubernetes-service-public-ip-address-of-the-review-apps-environment">Get the Kubernetes service public ip address of the Review Apps environment</h2>
<p>Go to the <code class="language-plaintext highlighter-rouge">Azure Pipelines Environment</code> tab and browse to find your <code class="language-plaintext highlighter-rouge">Kubernetes</code> Cluster as well as the new namespace generated for your Pull Request.</p>
<p><img src="/assets/2019-12-03/04-check-new-environment-for-pull-request.png" alt="04-check-new-environment-for-pull-request.png" /></p>
<p>Check the <code class="language-plaintext highlighter-rouge">Service</code> tab of your namespace and note the public IP address.</p>
<p><img src="/assets/2019-12-03/05-get-access-to-public-address.png" alt="05-get-access-to-public-address.png" /></p>
<p>Try to access it through your web browser and you should see your Pull Request environment! How awesome is that?
No more CSS blind reviews, no more technical reviews without functional reviews, <code class="language-plaintext highlighter-rouge">Review Apps</code> is a wonderful tool if you want added quality to your Pull requests !</p>
<h2 id="review-apps-from-now-on">Review Apps from now on</h2>
<p>Well, while this new functionality is still in preview, the principle is pretty simple yet really powerful!
Few things I am still looking forward to complete this scenario:</p>
<ul>
<li>Deployment through <code class="language-plaintext highlighter-rouge">helm</code></li>
<li>Pushing the public IP to the Pull Request comments (removing the need to look for it by ourselves in the <code class="language-plaintext highlighter-rouge">Environment</code> tab</li>
<li>A automatic clean of the pull Request namespace when the merge is done.</li>
</ul>
<p>You can have a look at my demo <a href="https://dev.azure.com/vivien/ReviewApps-Demo">Azure DevOps Team Project</a></p>
<p>Feel free to react in the comments or on Twitter <a href="https://twitter.com/vivienfabing">@vivienfabing</a>, and may the code be with you!</p>Vivien FabingHi everyone,