https://www.endpointdev.com/blog/tags/aspdotnet/2023-05-27T00:00:00+00:00End Point DevI wrote the same app twice, with Hotwire and Blazor Server — here’s what I learnedhttps://www.endpointdev.com/blog/2023/05/i-wrote-the-same-app-twice-with-hotwire-and-blazor-server/2023-05-27T00:00:00+00:00Kevin Campusano
<p><img src="/blog/2023/05/i-wrote-the-same-app-twice-with-hotwire-and-blazor-server/2022-09-14_193717.webp" alt="A dark sky sprawls over a tall canyon. Misty clouds hang on jagged peaks on the hill to the left. The other hill on the right rises at a steep angle, making a “V” shape. Both hills are covered in different shades of green, mostly a deep, dark green due to the late hour. Some sun peeks through the overcast sky to let through a bit of bluer light."></p>
<!-- Photo by Seth Jensen, 2022. -->
<p>There’s been a very interesting movement that has emerged recently in the world of frontend web development: a rise of little-to-no-JavaScript frontend frameworks.</p>
<p>The promise here is that we would be able to develop web applications with rich interactive capabilities without the need to write a whole lot of JavaScript. As such, these new approaches present themselves as alternatives to the likes of Vue, React, Angular, etc.</p>
<p>Two recent technologies that try to fulfill this promise come from two of the most prolific web application development frameworks of today: <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a>, built on .NET, and <a href="https://hotwired.dev/">Hotwire</a>, built on Ruby on Rails.</p>
<p>Now, I love my JS frameworks as much as the next guy, but these new technologies are intriguing. So I decided to build the same application twice, with Hotwire and with Blazor. I learned a few things along the way that I would like to share in this blog post.</p>
<blockquote>
<p>Note that there is a <a href="#table-of-contents">table of contents</a> at the end of this post.</p>
</blockquote>
<h3 id="what-this-article-is">What this article is</h3>
<p>I want to present some of my findings when working with these two technologies. I also want to discuss how they work and how they feel. How they are similar and how they are different. How they take different routes to arrive at their ultimately similar destinations. Maybe offer some pros and cons.</p>
<p>This post assumes sufficient familiarity with <a href="https://learn.microsoft.com/en-us/dotnet/csharp/">C#</a>, <a href="https://dotnet.microsoft.com/en-us/apps/aspnet">ASP.NET</a>, <a href="https://www.ruby-lang.org/en/">Ruby</a>, <a href="https://rubyonrails.org/">Ruby on Rails</a> and the current state of the art of web development. I won’t assume any familiarity with either Blazor or Hotwire, but this is not a tutorial for either, so I won’t explain in detail how to fully build apps with these technologies.</p>
<p>So who am I writing this for? Essentially, for anybody who is curious about these technologies and is interested in understanding the big picture of what they are about, how they compare to each other, and building their next project with one of them. So, this article is intended to serve more as an introduction to both, a starting point for a conversation to help you make a decision on what’s best for you and your team.</p>
<p>Spoiler alert: Both are great and you can’t go wrong with either. It all comes down to your team’s preferences and past experience.</p>
<p>One final thing worth noting is that I’m focusing this article on “<a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-7.0#blazor-server">Blazor Server</a>” specifically. I’ll be using the word “Blazor” moving forward, for short. Blazor as a framework has three variants: Blazor Server, Blazor WebAssembly and Blazor Hybrid. You can learn more about them <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-7.0">here</a>.</p>
<blockquote>
<p>Most of the examples that I’ll use in this post come from a couple of demo apps that I built in order to get my feet wet with these technologies. You can find both of them in GitHub. <a href="https://github.com/megakevin/quote-editor-blazor">Here’s the Blazor one</a> and <a href="https://github.com/megakevin/quote-editor-hotwire">here’s the Hotwire one</a>. You can study both the source code and the commit history, which I tried my best to keep neatly organized. They are both functionally identical, and based on <a href="https://www.hotrails.dev/turbo-rails">this excellent Hotwire tutorial</a>.</p>
</blockquote>
<h3 id="an-overview-of-blazor">An overview of Blazor</h3>
<p>When it comes to how they are designed and the developer experience they offer, these two are very different. Let’s go over some of the key details of Blazor and then we’ll do the same with Hotwire. With that, the differences between them will become apparent.</p>
<p>The first thing we have to understand about Blazor is that it is a component framework, very much like Vue or React. So, with Blazor, applications are broken up into composable modules called “<a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-7.0">Razor/Blazor components</a>” that are essentially independent pieces of GUI bundled with their corresponding logic. Each component has three parts to it:</p>
<ol>
<li>HTML-like markup that describes the layout and UI elements to be rendered,</li>
<li>C# logic that defines the behavior of the component, like what actions to take when users interact with GUI elements, and</li>
<li>CSS for styling the GUI elements.</li>
</ol>
<p>For example here’s a simple a Blazor component that allows displaying, editing, and deleting a particular type of record called “Quote”. Don’t worry about the details too much; we’ll go over some of them next. For now, I just want us to get a sense of what Blazor components look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">@using Microsoft.EntityFrameworkCore
@inject IDbContextFactory<QuoteEditorBlazor.Data.QuoteEditorContext> dbContextFactory
@if (isEditing)
{
<QuoteEditorBlazor.Shared.Quotes.Edit
QuoteToEdit=<span style="color:#d20;background-color:#fff0f0">"QuoteToShow"</span>
OnCancel=<span style="color:#d20;background-color:#fff0f0">"HideEditForm"</span>
/>
}
<span style="color:#080;font-weight:bold">else</span>
{
<div class=<span style="color:#d20;background-color:#fff0f0">"quote"</span>>
<a href=<span style="color:#d20;background-color:#fff0f0">"quotes/@QuoteToShow.ID"</span>>@QuoteToShow.Name</a>
<div class=<span style="color:#d20;background-color:#fff0f0">"quote__actions"</span>>
<a class=<span style="color:#d20;background-color:#fff0f0">"btn btn--light"</span> @onclick=<span style="color:#d20;background-color:#fff0f0">"DeleteQuote"</span>>Delete</a>
<a class=<span style="color:#d20;background-color:#fff0f0">"btn btn--light"</span> @onclick=<span style="color:#d20;background-color:#fff0f0">"ShowEditForm"</span>>Edit</a>
</div>
</div>
}
@code {
<span style="color:#369"> [Parameter]</span>
<span style="color:#080;font-weight:bold">public</span> QuoteEditorBlazor.Models.Quote QuoteToShow { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [Parameter]</span>
<span style="color:#080;font-weight:bold">public</span> EventCallback OnQuoteDeleted { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#888;font-weight:bold">bool</span> isEditing = <span style="color:#080;font-weight:bold">false</span>;
<span style="color:#080;font-weight:bold">void</span> ShowEditForm()
{
isEditing = <span style="color:#080;font-weight:bold">true</span>;
}
<span style="color:#080;font-weight:bold">void</span> HideEditForm()
{
isEditing = <span style="color:#080;font-weight:bold">false</span>;
}
<span style="color:#080;font-weight:bold">void</span> DeleteQuote()
{
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">var</span> context = dbContextFactory.CreateDbContext();
context.Quotes.Remove(QuoteToShow);
context.SaveChanges();
OnQuoteDeleted.InvokeAsync();
}
}
</code></pre></div><p>You can see that we have some C# in the file (enclosed in a <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0#code"><code>@code</code></a> block) with some event handlers and parameters. We also have some markup written with a mixture of HTML and C#. This markup has conditionals, wires up click event handlers, renders data from a given record, renders another component, etc. That markup is really just <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0">Razor</a>, a templating language that has been widely used in ASP.NET for a good while now. And we also have some top-level statements like <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0#using"><code>@using</code></a> and <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0#inject"><code>@inject</code></a> for including classes and objects that the component can use.</p>
<p>As far as CSS goes, <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-7.0">here’s how it works</a>: Suppose these are the contents of a file called <code>Example.razor</code>. The CSS for it would have to be defined in an <code>Example.razor.css</code> file sitting right next to it. Syntax-wise, this would just be a plain old CSS file. One cool thing to mention about it, though, is that the CSS within it is visible only to the component. So there’s no risk of conflicting rules between components.</p>
<p>If you’re familiar with modern frontend web development and have used frameworks like Vue or React, this should look very familiar to you. In fact, I would venture to say this is one of Blazor’s most attractive points. If you come from that background, and know .NET, it’s not that big of a leap to get into Blazor. The development experience is very similar as its design shares many concepts with modern JS frameworks; they operate under a very similar mental model.</p>
<p>Of course, C# is not JavaScript and .NET is not a browser. So there are many differences when it comes to the nitty-gritty mechanics of things. So a thorough understanding of the .NET framework and its <a href="https://learn.microsoft.com/en-us/dotnet/standard/framework-libraries">libraries</a> is also important for mastering Blazor. Which, depending on your team’s background, may be a point in favor or against.</p>
<h4 id="how-blazor-works-under-the-hood">How Blazor works under the hood</h4>
<p>As you can probably gather from the previous discussion, Blazor takes control of the entire GUI of the application and puts up a firm layer of abstraction over traditional native web technologies. All notion of JavaScript or code executing on a browser environment is greatly de-emphasized. In fact, most of Blazor executes in the server.</p>
<p>Essentially, all the code that you actually write executes on the server side. When a user begins using the app, a two-way, persistent <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/signalr">SignalR</a> connection is established between the browser and server. Whenever the client requests a page, the server renders it and sends it through the connection to the client browser, where there’s a component that interprets and displays it. Likewise, whenever the user interacts with some UI element, like clicking a button or a link, the action is sent to the server via this SignalR connection for it to be processed. If changes to the GUI are necessary, the server calculates them and sends them back to the user’s browser, where they will be interpreted and used to re-render the screen with the new state.</p>
<p><a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-7.0#blazor-server">Microsoft’s official documentation</a> has an excellent diagram that helps explain the process:</p>
<p><img src="/blog/2023/05/i-wrote-the-same-app-twice-with-hotwire-and-blazor-server/blazor-server.png" alt="On the left, a cloud icon sits in front of a server icon. The cloud is labeled “ASP.NET Core”, and has two smaller boxes labeled “Razor Components” and “.NET”. There are two arrows pointing to and from a web browser diagram, with the arrows labeled “SignalR”. The web browser diagram has a smaller box in it labeled “DOM”."></p>
<p>So, even though there is client side code running and browser DOM being manipulated, this is all happening under the hood. The developer doesn’t need to be concerned with that and can just focus on authoring C# code, for the most part.</p>
<p>This approach has a few implications worth noting. One is that this means higher load on the server compared to more traditional web applications. This is mainly because there needs to be a connection always open between clients and the server, by design. Classic HTTP is purely stateless, and connections are typically opened and closed multiple times throughout a user’s session, as they interact with the web app. Not so for Blazor, where this SignalR connection (most likely via <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSockets</a>) is always alive. So, for scalability concerns, we need to keep in mind that the more concurrent users our app has, the more resources the server will consume, even if the users are somewhat idle.</p>
<h4 id="an-abstraction-over-the-requestresponse-cycle">An abstraction over the request/response cycle</h4>
<p>A key element of Blazor is that it abstracts developers from the classic <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#http_messages">HTTP request/response model</a>. With Blazor, one seldom has to consider that aspect, as all interactions between client and server are managed via the framework itself through the persistent connection we discussed in the last couple of paragraphs. To the developer, there’s no real separation between the two. This is a big departure from classic <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/mvc">MVC</a>-like frameworks where things like endpoints, actions, and controllers are front and center. Such concepts are simply not in play on Blazor apps. The idea is to make them feel more like desktop apps: fully integrated, monolithic, simple packages of GUI and functionality.</p>
<p>This could become a double-edged sword as, in general, it is always important to have a clear understanding of the underlying technologies that support the application stack you’re working with. That is, the web is still there, even if you can’t see it. But as long as you’re cognizant of that, this approach can have great advantages too.</p>
<p>For example, thanks to this approach, there’s no need to employ the classic <a href="https://developer.mozilla.org/en-US/docs/Glossary/SPA">SPA pattern</a> of developing applications in two halves: 1. a backend Web API for domain logic written in some backend programming language, and 2. a frontend application written in JavaScript that implements the user experience and communicates with the backend over HTTP.</p>
<p>Blazor attempts to offer a simpler solution from a developer’s perspective, where everything lives in the same execution environment so there’s no need for inter-process communication over HTTP. The example Blazor component from before demonstrates such a case. Notice how this link has an event handler registered to its click event:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">a</span> <span style="color:#a61717;background-color:#e3d2d2">@</span><span style="color:#369">onclick</span>=<span style="color:#d20;background-color:#fff0f0">"DeleteQuote"</span>>Delete</<span style="color:#b06;font-weight:bold">a</span>>
</code></pre></div><p>That <code>DeleteQuote</code> event handler, simply defined as the method below, directly leverages the <a href="https://learn.microsoft.com/en-us/ef/ef6/fundamentals/working-with-dbcontext"><code>DbContext</code></a> to issue the delete command to the underlying database:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">void</span> DeleteQuote()
{
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">var</span> context = dbContextFactory.CreateDbContext();
context.Quotes.Remove(QuoteToShow);
context.SaveChanges();
}
</code></pre></div><p>Pretty simple.</p>
<p>However, this flexibility also requires great discipline from the development team. It falls upon us not to clutter the GUI code with all manner of extraneous things like database calls and domain or application logic that have nothing to do with user interface concerns. The traditional two-halves pattern for SPAs has this separation between domain and GUI logic baked in by necessity. Blazor allows us to break free from it, but that does not mean that the separation is not useful or even necessary. For a small example like this, this works just fine, but for larger applications, a more modularized design should be considered as well. Maybe the introduction of abstractions like <a href="https://martinfowler.com/eaaCatalog/repository.html">repositories</a> or <a href="https://martinfowler.com/bliki/EvansClassification.html">domain services</a>? At the end of the day, the core software design principles still need to be applied.</p>
<h4 id="how-blazor-supports-common-frontend-framework-features">How Blazor supports common frontend framework features</h4>
<p>Something else to consider, which I touched on before, is that Blazor is built on top of .NET. That means that a solid understanding of .NET concepts is all but a necessity in order to be effective with Blazor. Most of the features that are now traditional and expected in frontend JavaScript frameworks exist in Blazor, and they are implemented using age-old .NET concepts. If your team has solid .NET experience, this is a blessing. If not, then Blazor requires a larger investment, one that could be overwhelming depending on your time constraints.</p>
<p>Here are a few examples of how Blazor implements classic frontend framework features:</p>
<h5 id="handling-dom-events">Handling DOM events</h5>
<p>We already saw how <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-7.0">event handlers</a> work: they are defined as regular C# methods. The way they are registered to respond to events is via attributes in the GUI elements like <code>@onclick</code> or <code>@onchange</code>. All traditional DOM events are available. Like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">a</span> <span style="color:#a61717;background-color:#e3d2d2">@</span><span style="color:#369">onclick</span>=<span style="color:#d20;background-color:#fff0f0">"DeleteQuote"</span>>Delete</<span style="color:#b06;font-weight:bold">a</span>>
</code></pre></div><h5 id="including-other-blazor-components">Including other Blazor components</h5>
<p>We also saw how to render components from within other components. All it takes is adding the component to the template as if it was any other GUI element/HTML tag. The tag itself is the name of the component, which sometimes needs to be fully qualified. We saw an example before:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">QuoteEditorBlazor.Shared.Quotes.Edit</span>
<span style="color:#369">QuoteToEdit</span>=<span style="color:#d20;background-color:#fff0f0">"QuoteToShow"</span>
<span style="color:#369">OnCancel</span>=<span style="color:#d20;background-color:#fff0f0">"HideEditForm"</span>
/>
</code></pre></div><p>The fully qualified component name is <code>QuoteEditorBlazor.Shared.Quotes.Edit</code> in this case, and that’s how we reference it. If we were to include the namespace with an <code>@using</code> statement (like <code>@using QuoteEditorBlazor.Shared.Quotes</code> near the top of the file) then we would be able to invoke the component just by its name of <code>Edit</code>.</p>
<h5 id="passing-parameters-to-components">Passing parameters to components</h5>
<p>That previous snippet also demonstrates how to pass parameters to components. In this case, we have two: <code>QuoteToEdit</code> which is an object, and <code>OnCancel</code> which is a custom event. As you can see, parameters are passed as if they were HTML element attributes. In the case of <code>QuoteToEdit</code>, we’re passing it <code>QuoteToShow</code>, which is a <a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties">C# Property</a> defined in the <code>@code</code> section of the component:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">public</span> QuoteEditorBlazor.Models.Quote QuoteToShow { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
</code></pre></div><p>In order for the receiving component to be able to accept the parameter, it needs to define a property itself of the same type, annotated with the <code>Parameter</code> <a href="https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/">attribute</a>. In this case, the <code>QuoteEditorBlazor.Shared.Quotes.Edit</code> component defines it like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#369">[Parameter]</span>
<span style="color:#080;font-weight:bold">public</span> QuoteEditorBlazor.Models.Quote QuoteToEdit { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
</code></pre></div><p>Notice how the name of the property is the same name used for the “HTML attribute” that was used in markup to pass the parameter to the component:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript">QuoteToEdit=<span style="color:#d20;background-color:#fff0f0">"QuoteToShow"</span>
</code></pre></div><p>In this case, the expected parameter is of type <code>QuoteEditorBlazor.Models.Quote</code>.</p>
<h5 id="defining-handling-and-triggering-custom-component-events">Defining, handling and triggering custom component events</h5>
<p>The other parameter, which is a custom event, accepts a method. It looks like this in the receiving component:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#369">[Parameter]</span>
<span style="color:#080;font-weight:bold">public</span> EventCallback OnCancel { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
</code></pre></div><p>Again, this is a Property annotated with the <code>Parameter</code> Attribute. The only difference is that the type of this one is <code>EventCallback</code>. That’s what allows it to accept a method. Then, inside the receiving component, the event can be triggered with code like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">OnCancel.InvokeAsync();
</code></pre></div><p>This will execute whatever method the parent component has registered as a handler for this custom event. In this case, that would be our <code>HideEditForm</code> method.</p>
<h5 id="handling-component-lifecycle-events">Handling component lifecycle events</h5>
<p>Other than DOM and custom events, much like in other frontend frameworks, Blazor components also offer ways of hooking up to their own internal lifecycle events. <code>OnInitialized</code> is one of the most important ones, which runs when the component is first starting up. To hook into it and run some code when it happens, all a Blazor component has to do is implement it as a <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override">method override</a> within its code section. Something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">void</span> OnInitialized()
{
<span style="color:#888">// Do something here.
</span><span style="color:#888"></span>}
</code></pre></div><p>There’s more to learn about component lifecycle and <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0">the official documentation</a> does a good job in explaining it.</p>
<h5 id="templates">Templates</h5>
<p>Like we discussed before, Blazor also offers a rich templating syntax via Razor, which is also a hallmark of modern frontend frameworks. We have conditional rendering, support for loops, interpolation of data, model binding, etc.</p>
<h5 id="routing">Routing</h5>
<p>Routing is also included in Blazor and the way that it works is with the <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0#page"><code>@page</code></a> directive that’s used on top of components like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">@page <span style="color:#d20;background-color:#fff0f0">"/quotes"</span>
</code></pre></div><p>Not all components will use these, only those that correspond to whole pages. Most components will be used as portions of pages and as such, would not include these. A <code>@page</code> statement like the above will make the component that includes it accessible via an URL like <code>http://mydomain.com/quotes</code>. In other words, at the root of the site.</p>
<p>These routes also support parameters, which can be defined like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">@page <span style="color:#d20;background-color:#fff0f0">"/quotes/{quoteId:int}"</span>
</code></pre></div><p>A component with this directive will be accessible through URLs like <code>http://mydomain.com/quotes/123</code>. The code in the component can access the route parameter <code>quoteId</code> by defining it as a Property with the matching type, annotated with the <code>Parameter</code> attribute. Like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#369">[Parameter]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> QuoteId { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }.
</code></pre></div><h5 id="state-management">State management</h5>
<p>Global application state management à la <a href="https://vuex.vuejs.org/">Vuex</a> or <a href="https://redux.js.org/">Redux</a> is also available in Blazor. The cool thing about how this is implemented in Blazor is that there is no need for any additional library or special components. A global app store can be a simple C# object that’s configured to have a lifetime that spans that of the user’s session. Here’s an example of a class that’s used to store global flash messages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">QuoteEditorBlazor.AppState</span>;
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">FlashStore</span>
{
<span style="color:#080;font-weight:bold">public</span> List<<span style="color:#888;font-weight:bold">string</span>> Messages { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">set</span>; } = <span style="color:#080;font-weight:bold">new</span> List<<span style="color:#888;font-weight:bold">string</span>>();
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">event</span> Action? MessagesChanged;
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> <span style="color:#080;font-weight:bold">void</span> AddMessage(<span style="color:#888;font-weight:bold">string</span> message)
{
Messages.Add(message);
MessagesChanged?.Invoke();
<span style="color:#080;font-weight:bold">await</span> Task.Delay(TimeSpan.FromSeconds(<span style="color:#00d;font-weight:bold">5</span>));
Messages.Remove(message);
MessagesChanged?.Invoke();
}
}
</code></pre></div><p>Like I said: a plain and simple C# class. It offers a method for displaying a message for a few seconds. It does this by storing the given message into an internal variable and then, after a little while, it gets removed. It also offers a <code>MessagesChanged</code> <a href="https://learn.microsoft.com/en-us/dotnet/standard/events/">event</a> that other components can subscribe to which is triggered as messages are added and removed.</p>
<p>The lifetime of the instance is controlled via its <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0">dependency injection</a> configuration. In our <code>Program.cs</code>, it would be configured like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">builder.Services.AddScoped<FlashStore>();
</code></pre></div><p>Like I mentioned before, Blazor apps establish a persistent connection for the user throughout their session. This means that the instance of the app that is running on the server is also persistent. So, if we add <code>FlashStore</code> as a <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped">scoped</a> service, one single instance of it will also persist throughout the session. That’s why we can rely on its internal variables to store global application state.</p>
<p>You can learn more about state management in Blazor <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-7.0&pivots=server">here</a>.</p>
<p>Then, you could have a component that renders those messages that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">@using QuoteEditorBlazor.AppState
@inject FlashStore flashStore
@implements IDisposable
<div class=<span style="color:#d20;background-color:#fff0f0">"flash"</span>>
@foreach (<span style="color:#888;font-weight:bold">var</span> message <span style="color:#080;font-weight:bold">in</span> flashStore.Messages)
{
<div class=<span style="color:#d20;background-color:#fff0f0">"flash__message"</span>>
@message
</div>
}
</div>
@code {
<span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">void</span> OnInitialized()
{
flashStore.MessagesChanged += StateHasChanged;
}
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">void</span> Dispose()
{
flashStore.MessagesChanged -= StateHasChanged;
}
}
</code></pre></div><p>Very simple too. This Blazor component uses a loop to render all the messages. There are a couple of interesting things about this one. First, the component gets access to the instance of the <code>FlashStore</code> class via the <code>@inject</code> directive near the top of the file. That’s how the <code>flashStore</code> variable is made available for the component to use both in C# code and in the template.</p>
<p>The second interesting element is how this component registers its <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0#state-changes-statehaschanged"><code>StateHasChanged</code></a> method to the <code>MessagesChanged</code> event defined in <code>FlashStore</code>. As you recall, <code>FlashStore</code> will trigger <code>MessagesChanged</code> every time messages are added or removed. By registering <code>StateHasChanged</code> to that event, we make sure that the component re-renders every time the messages list changes; which in turn ensures that the most current messages are always rendered. Kind of a neat trick.</p>
<p>And as you might expect, any piece of code throughout the app can submit new messages to <code>FlashStore</code> by getting hold of the global instance via dependency injection and then calling:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">flashStore.AddMessage(<span style="color:#d20;background-color:#fff0f0">"Here's a flash message!"</span>);
</code></pre></div><h5 id="interoperability-with-javascript">Interoperability with JavaScript</h5>
<p>For all the nice abstractions that Blazor presents us with, the reality is that sometimes we actually do have to break through them. In the case of JavaScript, this happens when, for example, we need access to some native browser feature, or when we need to integrate with some library for some widget or other type of capability. In Blazor, this is certainly possible.</p>
<p>There are many details to consider when it comes to JavaScript interoperability in Blazor. Check out <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-7.0">the official documentation</a> to learn what all is possible. For our purposes here though, it’s enough to know that <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-7.0">calls from .NET to JS</a> and <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-7.0">vice versa</a> are supported.</p>
<p>The most basic example of calling JS code from .NET code looks like the following. If we have a JS function like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#038">window</span>.showMessagefromServer = (message) => {
alert(<span style="color:#d20;background-color:#fff0f0">`The server says: </span><span style="color:#33b;background-color:#fff0f0">${</span>message<span style="color:#33b;background-color:#fff0f0">}</span><span style="color:#d20;background-color:#fff0f0">`</span>);
<span style="color:#080;font-weight:bold">return</span> <span style="color:#d20;background-color:#fff0f0">"The client says thanks!"</span>;
};
</code></pre></div><p>Such a method can be invoked from a Blazor component like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888;font-weight:bold">string</span> interopResult = <span style="color:#080;font-weight:bold">await</span> js.InvokeAsync<<span style="color:#888;font-weight:bold">string</span>>(<span style="color:#d20;background-color:#fff0f0">"showMessagefromServer"</span>, <span style="color:#d20;background-color:#fff0f0">"Have a nice day!"</span>);
</code></pre></div><p>The Blazor component that runs this code would have to inject an instance of <code>IJSRuntime</code> into the <code>js</code> variable with a statement like <code>@inject IJSRuntime js</code>. A key thing to note is that the JavaScript method needs to be attached to the <code>window</code> object in order for it to be accessible.</p>
<p>Like I said, the other way around also works: JavaScript code is able to call .NET logic defined in a server-side Blazor component. Here’s a simple example for us to get an idea of how it feels. We can have a method that looks like this on a Blazor component:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#369">[JSInvokable]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">static</span> Task<<span style="color:#888;font-weight:bold">string</span>> getMessageFromServer()
{
<span style="color:#080;font-weight:bold">return</span> Task.FromResult(<span style="color:#d20;background-color:#fff0f0">"Have a nice day!"</span>);
}
</code></pre></div><p>This method is public and <a href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members">static</a>. It is also possible to invoke non-static instance methods, but the setup is a little more complicated, and this is enough for our purposes here. Other than that, as you can see, the method needs to be annotated with the <code>JSInvokable</code> attribute and return a type of <code>Task</code>.</p>
<p>Here’s some JavaScript that calls this method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-js" data-lang="js"><span style="color:#038">window</span>.returnArrayAsync = () => {
DotNet.invokeMethodAsync(<span style="color:#d20;background-color:#fff0f0">'{YOUR APP ASSEMBLY NAME}'</span>, <span style="color:#d20;background-color:#fff0f0">'getMessageFromServer'</span>)
.then(data => {
alert(<span style="color:#d20;background-color:#fff0f0">`I asked the server and it says: </span><span style="color:#33b;background-color:#fff0f0">${</span>data<span style="color:#33b;background-color:#fff0f0">}</span><span style="color:#d20;background-color:#fff0f0">`</span>);
});
};
</code></pre></div><p>There are a few noteworthy things here. First we have the <code>DotNet.invokeMethodAsync</code> function which Blazor makes available to us in JavaScript land. That’s what we use to invoke .NET server side code. Next, the function needs to be given the <a href="https://learn.microsoft.com/en-us/dotnet/standard/assembly/">assembly</a> name of our app as well as the name of the method to invoke within the Blazor component. This is the one that was annotated with <code>JSInvokable</code>. Finally, the function itself is asynchronous and thus returns a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promise</a>.</p>
<h3 id="an-overview-of-hotwire">An overview of Hotwire</h3>
<p><a href="https://hotwired.dev/">Hotwire</a> makes a promise similar to Blazor’s. However, the way it goes about it couldn’t be more different.</p>
<p>Hotwire is much simpler and more minimalistic than Blazor. Whereas in Blazor we let the framework take total control of the GUI, just like we do with other popular frontend frameworks, Hotwire feels more like a natural evolution of traditional pre-JavaScript-heavy web development. We still have <a href="https://guides.rubyonrails.org/action_controller_overview.html">controllers</a> and <a href="https://guides.rubyonrails.org/action_view_overview.html">views</a>, we still render pages on the server, and we still work in tandem with HTTP’s request/response model.</p>
<p>What Hotwire gives us, if we were to boil it down to a single sentence, is a way to refresh only portions of our pages as a result of user interactions. That is, we’re not forced to reload the entire page, as is the case with non-JavaScript web applications. For example, in Hotwire we have the ability to submit a form or click a link and, as a result of that, only update a particular message, picture, or section.</p>
<blockquote>
<p>ASP.NET veterans will find this awfully familiar. That’s because all the way back in version 3.5, <a href="https://learn.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-getting-started/aspnet-ajax/">ASP.NET AJAX</a> offered a very similar feature: partial page updates with the UpdatePanel component. Indeed, Hotwire presents a very similar concept; only greatly improved and modernized.</p>
</blockquote>
<p>When it comes to actual coding, Hotwire’s footprint is minimal. It augments traditional Ruby on Rails controllers and views to produce the desired effect of page refreshes that are partial and targeted. Let’s walk through an example and you’ll see how we can start with a regular looking Rails app and then, through minor adjustments, we end up with a more richly interactive experience.</p>
<p>Imagine we are beginning to develop support for CRUDing a particular type of record called “Quote”, and we have these files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#888">### app/controllers/quotes_controller.rb</span>
<span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">QuotesController</span> < <span style="color:#036;font-weight:bold">ApplicationController</span>
<span style="color:#080;font-weight:bold">def</span> <span style="color:#06b;font-weight:bold">index</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">def</span> <span style="color:#06b;font-weight:bold">new</span>
<span style="color:#33b">@quote</span> = <span style="color:#036;font-weight:bold">Quote</span>.new
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">end</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#888"><!-- app/views/quotes/index.html.erb --></span>
<<span style="color:#b06;font-weight:bold">main</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"container"</span>>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"header"</span>>
<<span style="color:#b06;font-weight:bold">h1</span>>Quotes</<span style="color:#b06;font-weight:bold">h1</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= link_to "New quote", new_quote_path %>
</<span style="color:#b06;font-weight:bold">div</span>>
</<span style="color:#b06;font-weight:bold">main</span>>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#888"><!-- app/views/quotes/new.html.erb --></span>
<<span style="color:#b06;font-weight:bold">main</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"container"</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= link_to "Back to quotes", quotes_path %>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"header"</span>>
<<span style="color:#b06;font-weight:bold">h1</span>>New quote</<span style="color:#b06;font-weight:bold">h1</span>>
</<span style="color:#b06;font-weight:bold">div</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= render "form", quote: @quote %>
</<span style="color:#b06;font-weight:bold">main</span>>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#888"><!-- app/views/quotes/_form.html.erb --></span>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= simple_form_for quote do |f| %>
<span style="color:#a61717;background-color:#e3d2d2"><</span>% if quote.errors.any? %>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"error-message"</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= quote.errors.full_messages.to_sentence.capitalize %>
</<span style="color:#b06;font-weight:bold">div</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>% end %>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= f.input :name %>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= f.submit %>
<span style="color:#a61717;background-color:#e3d2d2"><</span>% end %>
</code></pre></div><blockquote>
<p>This sample is using the <code>simple_form</code> gem, which you can learn more about <a href="https://github.com/heartcombo/simple_form">here</a>.</p>
</blockquote>
<p>If you’re familiar with Rails, then you understand what’s happening here. We have a simple <code>index</code> page with a heading and a link to another page. That other page contains a form to create new Quote records. It also contains a link to go back to the <code>index</code> page.</p>
<h4 id="partial-page-updates-with-turbo-frames">Partial page updates with Turbo Frames</h4>
<p>As they are right now, these files would produce a traditional web application user experience. When links are clicked, the whole screen will be reloaded to show the page that the clicked link points to. But what if we wanted, for example, to have the new record creation form appear out of nowhere within the same <code>index</code> page, without a full page reload? Here’s what that would look like with Hotwire:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> <!-- app/views/quotes/index.html.erb -->
<main class="container">
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote",
new_quote_path,
<span style="color:#000;background-color:#dfd">+ data: { turbo_frame: dom_id(Quote.new) } %>
</span><span style="color:#000;background-color:#dfd"></span> </div>
<span style="color:#000;background-color:#dfd">+ <%= turbo_frame_tag Quote.new %>
</span><span style="color:#000;background-color:#dfd"></span> </main>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> <!-- app/views/quotes/new.html.erb -->
<main class="container">
<%= link_to sanitize("&larr; Back to quotes"), quotes_path %>
<div class="header">
<h1>New quote</h1>
</div>
<span style="color:#000;background-color:#dfd">+ <%= turbo_frame_tag Quote.new do %>
</span><span style="color:#000;background-color:#dfd"></span> <%= render "form", quote: @quote %>
<span style="color:#000;background-color:#dfd">+ <% end %>
</span><span style="color:#000;background-color:#dfd"></span> </main>
</code></pre></div><blockquote>
<p>Remember that these examples are taken from a fully working application. Feel free to read through <a href="https://github.com/megakevin/quote-editor-hotwire">the source code</a> to have a more complete understanding of the context within which these files exist.</p>
</blockquote>
<p>And that’s really all it takes. Let’s go over it. With these changes, whenever a user clicks on the “New quote” link, instead of the browser triggering the usual GET request to then reload the screen and show the creation page, Hotwire’s frontend component captures the click event and makes the request itself via <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XHR</a>. The server then receives this request normally and routes it to the <code>new</code> action in the <code>quotes</code> controller. All that action does is render the <code>new.html.erb</code> template and send that back to the client as a response.</p>
<p>When the Hotwire frontend component receives this, it notices that some of the response is enclosed in a <code>turbo_frame_tag</code> whose ID is that of an empty new Quote object. Now, because the link that the user clicked also had the ID of an empty new Quote object as its <code>data-turbo_frame</code> attribute, Hotwire looks for a <code>turbo_frame_tag</code> with the same ID in the page that’s currently being shown an replaces its contents with the contents from the similarly named <code>turbo_frame_tag</code> from the incoming response.</p>
<p>With that, you’ve seen in action some of the key elements that make Hotwire work. First of all we have the <code>turbo_frame_tag</code> helper, which produces a so-called “<a href="https://turbo.hotwired.dev/handbook/frames">Turbo Frame</a>”. Turbo Frames are the main building block that we use for partial page updates. Turbo Frames essentially say: “This section of the page is allowed to be dynamically updated without a full page refresh.” In an app that uses Hotwire, whenever you’re in a page that includes a Turbo Frame, if a request is made (whether it be navigation or form submission), and if the resulting response includes within it another Turbo Frame with the same ID, then Hotwire will notice the match and update the current page’s Turbo Frame with the contents of the Turbo Frame from the response.</p>
<p>Looking back at the code, you can see how we achieved this. We added an empty Turbo Frame on the index page, right below the link to the creation page. We also wrapped the form from the creation page in a similarly named Turbo Frame. Finally, we added the <code>data-turbo_frame</code> attribute to the link in the index page to tell Hotwire that it should kick in for this link and target that specific Turbo Frame. If the link was inside the Turbo Frame, we would not have to do this. Since it is outside, Hotwire needs the little hint that says: “Treat this link as if it was inside this Turbo Frame.”</p>
<p>I feel like this is at the same time a little awkward to wrap your head around and deceptively simple. When compared to Blazor, which builds upon tried and true concepts (as far as the developer experience goes at least), Hotwire almost seems alien, with a much more unusual style. But all in all, one can’t deny just how clean and simple all of this looks, in that “Rails magic” kind of way. And once it clicks, you can begin to see a world of possibilities opening up. The Hotwire developers managed to identify and extract a general design pattern of web application interactions, one that can be leveraged to produce a lot of varying rich interactive user experiences.</p>
<p>One neat aspect worth noting is that, if the user were to disable JavaScript, the app would still fully work. It would <a href="https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation">gracefully degrade</a>. This is a direct consequence of Hotwire’s paradigm of adding minimal features on top of the existing traditional Rails programming model.</p>
<h4 id="imperative-rendering-with-turbo-streams">Imperative rendering with Turbo Streams</h4>
<p>Let’s consider another example now. This time we’ll see another key component of Hotwire in action which is called <a href="https://turbo.hotwired.dev/handbook/streams">Turbo Streams</a>.</p>
<p>Turbo Streams helps solve the problem that emerges when the declarative style of pure Turbo Frames is not enough to obtain the fine grained control and behavior that we need. In these cases, the server needs to issue specific commands to the frontend on how to update the GUI. Via Turbo Streams, we can achieve just that. Coding-wise, these look like regular Rails view templates, albeit with some funky syntax thanks to the Turbo Stream helpers.</p>
<p>In our example, we’ll add a list of quotes in the index page, right below the link and the form. We’ll also complete our quote creation implementation so that we’re actually able to submit the form. As a result of that, we want the GUI to be updated so that the form goes away and the newly created quote is shown in the list. These last two are the things that we’ll use Turbo Streams for. Of course, these updates to the page will be done without a full page refresh.</p>
<p>Let’s start by adding a list of quotes on the index page.</p>
<p>In the controller, we query the database for all the quote records and store them in a variable that the view can later access.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> ### app/controllers/quotes_controller.rb
class QuotesController < ApplicationController
def index
<span style="color:#000;background-color:#dfd">+ @quotes = Quote.all
</span><span style="color:#000;background-color:#dfd"></span> end
def new
@quote = Quote.new
end
end
</code></pre></div><p>In the index view template, we render the collection of records.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> <!-- app/views/quotes/index.html.erb -->
<main class="container">
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote",
new_quote_path,
data: { turbo_frame: dom_id(Quote.new) } %>
</div>
<%= turbo_frame_tag Quote.new %>
<span style="color:#000;background-color:#dfd">+ <%= render @quotes %>
</span><span style="color:#000;background-color:#dfd"></span> </main>
</code></pre></div><p>Now we need to define a “_quote” partial view so that render statement we added on the index view can work <a href="https://thesaurus.plus/img/synonyms/128/automagically.png">automagically</a>.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#888"><!-- app/views/quotes/_quote.html.erb --></span>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"quote"</span>>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= quote.name %>
</<span style="color:#b06;font-weight:bold">div</span>>
</code></pre></div><p>With this, the <code>render @quotes</code> statement will loop through all the records in <code>@quotes</code> and render this partial view for each on of them. This template is very simple. All it does is render the name of the quote.</p>
<p>Now let’s add an action that can accept quote creation form submissions:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> ### app/controllers/quotes_controller.rb
class QuotesController < ApplicationController
def index
@quotes = Quote.all
end
def new
@quote = Quote.new
end
<span style="color:#000;background-color:#dfd">+
</span><span style="color:#000;background-color:#dfd">+ def create
</span><span style="color:#000;background-color:#dfd">+ @quote = Quote.new(quote_params)
</span><span style="color:#000;background-color:#dfd">+
</span><span style="color:#000;background-color:#dfd">+ if @quote.save
</span><span style="color:#000;background-color:#dfd">+ redirect_to quotes_path, notice: "Quote was successfully created."
</span><span style="color:#000;background-color:#dfd">+ else
</span><span style="color:#000;background-color:#dfd">+ render :new
</span><span style="color:#000;background-color:#dfd">+ end
</span><span style="color:#000;background-color:#dfd">+ end
</span><span style="color:#000;background-color:#dfd">+
</span><span style="color:#000;background-color:#dfd">+ def quote_params
</span><span style="color:#000;background-color:#dfd">+ params.require(:quote).permit(:name)
</span><span style="color:#000;background-color:#dfd">+ end
</span><span style="color:#000;background-color:#dfd"></span> end
</code></pre></div><p>A typical Rails recipe for a record creation endpoint. It takes the parameters coming from the request and uses them to create a new record via the <code>Quote</code> <a href="https://guides.rubyonrails.org/active_record_basics.html">Active Record</a> model. If successful, it redirects to the index page; if not, it renders the creation page again (via the <code>new</code> action). The <code>new.html.erb</code> view template has some logic to render error messages when they are present so those are going to show up when that page is rendered as a result of unsuccessful calls to this <code>create</code> endpoint.</p>
<p>At this point, we’re able to view all the quotes on record and create new ones. Now here are the changes to Hotwire-ify this scenario.</p>
<p>First we wrap the list of quotes with a Turbo Frame named “<code>quotes</code>":</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> <!-- app/views/quotes/index.html.erb -->
<main class="container">
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote",
new_quote_path,
data: { turbo_frame: dom_id(Quote.new) } %>
</div>
<%= turbo_frame_tag Quote.new %>
<span style="color:#000;background-color:#dfd">+ <%= turbo_frame_tag "quotes" do %>
</span><span style="color:#000;background-color:#dfd"></span> <%= render @quotes %>
<span style="color:#000;background-color:#dfd">+ <% end %>
</span><span style="color:#000;background-color:#dfd"></span> </main>
</code></pre></div><p>Next, we employ Turbo Streams. Like I said, Turbo Streams materialize themselves in code as if they were view templates. So, a new file is added that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#888"><!-- app/views/quotes/create.turbo_stream.erb --></span>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= turbo_stream.prepend "quotes", partial: "quotes/quote", locals: { quote: @quote } %>
<span style="color:#a61717;background-color:#e3d2d2"><</span>%= turbo_stream.update Quote.new, "" %>
</code></pre></div><p>It sort of looks like a couple of imperative statements, does it not? The first line instructs Hotwire to prepend, in the <code>"quotes"</code> Turbo Frame, a new render of the <code>app/views/quotes/_quote.html.erb</code> partial view, while passing it the <code>@quote</code> object as a parameter. The second line updates the <code>Quote.new</code> Turbo Frame to be empty. When we remember that the <code>"quotes"</code> Turbo Frame is the one that contains the list of records and the <code>Quote.new</code> Turbo Frame is the one that contains the new quote creation form, this starts to make sense. This Turbo Streams view is making the newly created quote appear in the list; and at the same time, it is making the form disappear. From a user’s perspective, this all takes place after submitting the creation form. So the user experience makes complete sense. All that with no full page refresh.</p>
<p>And finally, the controller action needs to make use of this new view template like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> ### app/controllers/quotes_controller.rb
def create
@quote = Quote.new(quote_params)
if @quote.save
<span style="color:#000;background-color:#fdd">- redirect_to quotes_path, notice: "Quote was successfully created."
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ respond_to do |format|
</span><span style="color:#000;background-color:#dfd">+ format.html { redirect_to quotes_path, notice: "Quote was successfully created." }
</span><span style="color:#000;background-color:#dfd">+ format.turbo_stream
</span><span style="color:#000;background-color:#dfd">+ end
</span><span style="color:#000;background-color:#dfd"></span> else
render :new, status: :unprocessable_entity
end
end
</code></pre></div><p>This is yet another familiar Rails pattern. This is how we specify different <a href="https://guides.rubyonrails.org/action_controller_overview.html#rendering-xml-and-json-data">response formats</a>, whether it be HTML, JSON, XML. Now, thanks to Hotwire, we can also specify Turbo Streams. This is one of the great aspects about Hotwire: it seamlessly integrates with Rails' existing features and concepts.</p>
<p>Anyway, in this case, we invoke <code>format.turbo_stream</code> within the block passed to <code>respond_to</code> and that makes it so the <code>app/views/quotes/create.turbo_stream.erb</code> view template is included in this action’s response. Hotwire’s frontend component sees this coming as part of the response and acts accordingly, updating the GUI how it’s been specified.</p>
<h4 id="adding-javascript-with-stimulus">Adding JavaScript with Stimulus</h4>
<p>The final piece of the Hotwire puzzle is <a href="https://stimulus.hotwired.dev/">Stimulus</a>, which allows us to integrate JavaScript functionality in a neat way. Stimulus is a very simple JavaScript framework, it does not take control of the entire UI. In fact, it does not render any HTML at all. Stimulus essentially offers a nice way of wiring up JS behavior to existing markup. Let’s look at a quick example of how it could hypothetically be used for showing a confirmation popup before deleting a record.</p>
<p>The JavaScript logic lives inside so-called “Stimulus controllers”. For our example, it could look something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#888">// app/javascript/controllers/confirmations_controller.js
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">import</span> { Controller } from <span style="color:#d20;background-color:#fff0f0">"@hotwired/stimulus"</span>
<span style="color:#080;font-weight:bold">export</span> <span style="color:#080;font-weight:bold">default</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#080;font-weight:bold">extends</span> Controller {
confirm() {
<span style="color:#080;font-weight:bold">if</span> (confirm(<span style="color:#d20;background-color:#fff0f0">"Are you sure you want to delete this record?"</span>)) {
<span style="color:#888">// Carry on with the operation...
</span><span style="color:#888"></span> }
}
}
</code></pre></div><p>And now, the idea is to wire up this code so that it runs when the hypothetical delete button is clicked. Here’s what that button could like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">button</span>
<span style="color:#369">data-controller</span>=<span style="color:#d20;background-color:#fff0f0">"confirmations"</span>
<span style="color:#369">data-action</span>=<span style="color:#d20;background-color:#fff0f0">"click->confirmations#confirm"</span>
>
Delete
</<span style="color:#b06;font-weight:bold">button</span>>
</code></pre></div><p>Pretty self-explanatory. We specify the name of the controller to use via the <code>data-controller</code> attribute. We also specify, via <code>data-action</code>, what method to invoke within that controller as a result of which DOM event, <code>click</code> in this case.</p>
<p>And that’s basically all it takes to sprinkle some JavaScript on Hotwire apps. Stimulus does offer a few more useful features, which you can read more about in <a href="https://stimulus.hotwired.dev/handbook/introduction">the official documentation</a>. But for us for now, it’s enough to know that it exists, and what’s the main idea behind it.</p>
<p>So as you can hopefully see, Hotwire is much smaller in scope to Blazor. And yet, it allows us to do just the same: highly interactive applications with little to no JavaScript. This ought to make a lot of Rails developers happy.</p>
<h3 id="a-final-comparison">A final comparison</h3>
<p>I was initially thinking about ending this blog post with a flowchart of sorts to explain the process of deciding which of these two technologies you should use. But really, the decision is very simple: If your team is comfortable with .NET, use Blazor. If your team is comfortable with Ruby on Rails, use Hotwire. It’s obvious, so I won’t claim to have made a great discovery here.</p>
<p>The only thing to add is that if your team is familiar with modern frontend web development framework concepts, you’ll be even better served by Blazor and you’ll hit the ground running. If not, then even for seasoned .NET people, there will be a decent learning curve, but not steep enough to be deterred. Moreover, if your team has no modern frontend development experience at all, then Hotwire is a godsend; thanks to its “augment classic backend-heavy web app development” style.</p>
<p>With that said, let’s close out with a summary of main aspects of both technologies and how they compare to each other.</p>
<p>Overall, Hotwire is much simpler than Blazor. While Blazor is a full-fledged GUI component framework, Hotwire’s approach is more like an augmentation of classic non-JavaScript web development patterns. That said, Hotwire’s style is more unusual than Blazor’s, so if your team is already familiar with modern frontend web development, Blazor can be a great fit.</p>
<p>While both frameworks try to offer enough functionality to allow the development of rich interactive experiences without the need to write any JavaScript, the reality is that sometimes JavaScript does need to be written. Both technologies offer ways to make this happen. And while both are perfectly workable, Blazor’s solution is a bit more clunky than Hotwire’s.</p>
<p>When it comes to classic web technologies like HTTP’s request/response cycle and the separation between server and client, Blazor’s style offers a big deviation from them. It greatly de-emphasizes them and presents instead a completely different programming model, one more akin to desktop application development. The concepts of request, response, client, and server seem to vanish. Not so for Hotwire, which builds upon these classic technologies in a way where they still need to be considered and are in fact in the spotlight. While Blazor attempts to do away with these, Hotwire embraces them.</p>
<p>In Blazor, client events are sent to the server, the server renders the DOM updates and sends them to the client for updates. This happens via the persistent SignalR/WebSockets connection.</p>
<p>Hotwire, on the other hand, intercepts client events and sends classic HTTP requests (via AJAX/XHR) to the server. The server then executes the requests and sends back the responses to the client which carries out the necessary operations, generally speaking, updating sections of the page that’s already being displayed.</p>
<p>That means that at the end of the day, both frameworks do the rendering on the server side and send the rendered markup over the wire to the clients. But in Blazor, the client and server have a persistent connection, while Hotwire’s connections come and go as normal HTTP requests and responses.</p>
<p>A neat aspect of Hotwire’s programming model is that it allows an incremental approach to web development where you can start developing the app like you would a traditional, non-reactive, non-JS app, then augment it with a little code to give it SPA capabilities.</p>
<p>And that’s all for now! I for one am glad to see these types of technologies emerge. While there are many teams out there that are already effective and productive with the current landscape of frontend web development, these two are very interesting and seem capable in their own right.</p>
<p>Besides, having alternatives is never a bad thing. Depending mainly on your previous experience, these could be a great fit for projects new and old. It’s great to know that both .NET and Rails include these types of offerings and that they work pretty well.</p>
<h3 id="table-of-contents">Table of contents</h3>
<ul>
<li><a href="#what-this-article-is">What this article is</a></li>
<li><a href="#an-overview-of-blazor">An overview of Blazor</a>
<ul>
<li><a href="#how-blazor-works-under-the-hood">How Blazor works under the hood</a></li>
<li><a href="#an-abstraction-over-the-requestresponse-cycle">An abstraction over the request/response cycle</a></li>
<li><a href="#how-blazor-supports-common-frontend-framework-features">How Blazor supports common frontend framework features</a>
<ul>
<li><a href="#handling-dom-events">Handling DOM events</a></li>
<li><a href="#including-other-blazor-components">Including other Blazor components</a></li>
<li><a href="#passing-parameters-to-components">Passing parameters to components</a></li>
<li><a href="#defining-handling-and-triggering-custom-component-events">Defining, handling and triggering custom component events</a></li>
<li><a href="#handling-component-lifecycle-events">Handling component lifecycle events</a></li>
<li><a href="#templates">Templates</a></li>
<li><a href="#routing">Routing</a></li>
<li><a href="#state-management">State management</a></li>
<li><a href="#interoperability-with-javascript">Interoperability with JavaScript</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#an-overview-of-hotwire">An overview of Hotwire</a>
<ul>
<li><a href="#partial-page-updates-with-turbo-frames">Partial page updates with Turbo Frames</a></li>
<li><a href="#imperative-rendering-with-turbo-streams">Imperative rendering with Turbo Streams</a></li>
<li><a href="#adding-javascript-with-stimulus">Adding JavaScript with Stimulus</a></li>
</ul>
</li>
<li><a href="#a-final-comparison">A final comparison</a></li>
</ul>
Implementing Backend Tasks in ASP.NET Corehttps://www.endpointdev.com/blog/2022/08/implementing-backend-tasks-in-asp.net-core/2022-08-08T00:00:00+00:00Kevin Campusano
<p><img src="/blog/2022/08/implementing-backend-tasks-in-asp.net-core/mountain-path.webp" alt=""></p>
<!-- Photo by Seth Jensen -->
<p>As we’ve <a href="https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/">already established</a>, <a href="https://rubyonrails.org/">Ruby on Rails</a> is great. The amount and quality of tools that Rails puts at our disposal when it comes to developing web applications is truly outstanding. One aspect of web application development that Rails makes particularly easy is that of creating backend tasks.</p>
<p>These tasks can be anything from database maintenance, file system cleanup, overnight heavy computations, bulk email dispatch, etc. In general, functionality that is typically initiated by a sysadmin in the backend, or scheduled in a <a href="https://en.wikipedia.org/wiki/Cron">cron</a> job, which has no GUI, but rather, is invoked via command line.</p>
<p>By integrating with <a href="https://github.com/ruby/rake">Rake</a>, Rails allows us to <a href="https://guides.rubyonrails.org/command_line.html#custom-rake-tasks">very easily write such tasks</a> as plain old <a href="https://ruby-doc.org/">Ruby</a> scrips. These scripts have access to all the domain logic and data that the full-fledged Rails app has access to. The cherry on top is that the command-line interface to invoke such tasks is very straightforward. It looks something like this: <code>bin/rails fulfillment:process_new_orders</code>.</p>
<p>All this is included right out of the box for new Rails projects.</p>
<p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet">ASP.NET Core</a>, which is also great, doesn’t support this out of the box like Rails does.</p>
<p>However, I think we should be able to implement our own without too much hassle, and have a similar sysadmin experience. Let’s see if we can do it.</p>
<blockquote>
<p>There is a <a href="#table-of-contents">Table of contents</a> at the end of this post.</p>
</blockquote>
<h3 id="what-we-want-to-accomplish">What we want to accomplish</h3>
<p>So, to put it in concrete terms, we want to create a backend task that has access to all the logic and data of an existing ASP.NET Core application. The task should be callable via command-line interface, so that it can be easily executed via the likes of cron or other scripts.</p>
<p>In order to meet these requirements, we will create a new .NET console app that:</p>
<ol>
<li>References the existing ASP.NET Core project.</li>
<li>Loads all the classes from it and makes instances of them available via <a href="https://en.wikipedia.org/wiki/Dependency_injection">dependency injection</a>.</li>
<li>Has a usable, Unix-like command-line interface that sysadmins would be familiar with.</li>
<li>Is invokable via the <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/">.NET CLI</a>.</li>
</ol>
<p>We will do all this within the context of an existing web application. One that I’ve been <a href="https://www.endpointdev.com/blog/2021/07/dotnet-5-web-api/">building upon</a> <a href="https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/">thoughout a few</a> <a href="https://www.endpointdev.com/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/">articles</a>.</p>
<p>It is a simple ASP.NET Web API backed by a <a href="https://www.postgresql.org/">Postgres</a> database. It has a few endpoints for <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>ing automotive related data and for calculating values of vehicles based on various aspects of them.</p>
<p>You can find the code <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api">on GitHub</a>. If you’d like to follow along, clone the repository and check out this commit: <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/9a078015cec2e8a42a4898e203a1a50db69731e8">9a078015ce</a>. It represents the project as it was before applying all the changes from this article. The finished product can be found <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/backend-task">here</a>.</p>
<p>You can follow the instructions in <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/json-web-token/VehicleQuotes/README.md">the project’s README file</a> if you want to get the app up and running.</p>
<p>For our demo use case, we will try to develop a backend task that creates new user accounts for our existing application.</p>
<p>Let’s get to it.</p>
<h3 id="creating-a-new-console-app-that-references-the-existing-web-app-as-a-library">Creating a new console app that references the existing web app as a library</h3>
<p>The codebase is structured as a <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-sln">solution</a>, as given away by the <code>vehicle-quotes.sln</code> file located at the root of the repository. Within this solution, there are two projects: <code>VehicleQuotes</code> which is the web app itself, and <code>VehicleQuotes.Tests</code> which contains the app’s test suite. For this article, we only care about the web app.</p>
<p>Like I said, the backend task that we will create is nothing fancy in itself. It’s a humble console app. So, we start by asking the <code>dotnet</code> CLI to create a new console app project for us.</p>
<p>From the repository’s root directory, we can do so with this command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet new console -o VehicleQuotes.CreateUser
</code></pre></div><p>That should’ve resulted in a new <code>VehicleQuotes.CreateUser</code> directory being created, and within it, (along with some other nuts and bolts) our new console app’s <code>Program.cs</code> (the code) and <code>VehicleQuotes.CreateUser.csproj</code> (the project definition) files. The name that we’ve chosen is straightforward: the name of the overall solution and the action that this console app is going to perform.</p>
<blockquote>
<p>There’s more info regarding the <code>dotnet new</code> command in <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new">the official docs</a>.</p>
</blockquote>
<p>Now, since we’re using a solution file, let’s add our brand new console app project to it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet sln add VehicleQuotes.CreateUser
</code></pre></div><p>OK, cool. That should’ve produced the following diff on <code>vehicle-quotes.sln</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#333">diff --git a/vehicle-quotes.sln b/vehicle-quotes.sln
</span><span style="color:#333">index 537d864..5da277d 100644
</span><span style="color:#333"></span><span style="color:#000;background-color:#fdd">--- a/vehicle-quotes.sln
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+++ b/vehicle-quotes.sln
</span><span style="color:#000;background-color:#dfd"></span><span style="color:#666">@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VehicleQuotes", "VehicleQuo
</span><span style="color:#666"></span> EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VehicleQuotes.Tests", "VehicleQuotes.Tests\VehicleQuotes.Tests.csproj", "{5F6470E4-12AB-4E30-8879-3664ABAA959D}"
EndProject
<span style="color:#000;background-color:#dfd">+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VehicleQuotes.CreateUser", "VehicleQuotes.CreateUser\VehicleQuotes.CreateUser.csproj", "{EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}"
</span><span style="color:#000;background-color:#dfd">+EndProject
</span><span style="color:#000;background-color:#dfd"></span> Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
<span style="color:#666">@@ -44,5 +46,17 @@ Global
</span><span style="color:#666"></span> {5F6470E4-12AB-4E30-8879-3664ABAA959D}.Release|x64.Build.0 = Release|Any CPU
{5F6470E4-12AB-4E30-8879-3664ABAA959D}.Release|x86.ActiveCfg = Release|Any CPU
{5F6470E4-12AB-4E30-8879-3664ABAA959D}.Release|x86.Build.0 = Release|Any CPU
<span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|x64.ActiveCfg = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|x64.Build.0 = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|x86.ActiveCfg = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Debug|x86.Build.0 = Debug|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|Any CPU.Build.0 = Release|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|x64.ActiveCfg = Release|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|x64.Build.0 = Release|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|x86.ActiveCfg = Release|Any CPU
</span><span style="color:#000;background-color:#dfd">+ {EDBB33E3-DCCE-4957-8A69-DC905D1BEAA4}.Release|x86.Build.0 = Release|Any CPU
</span><span style="color:#000;background-color:#dfd"></span> EndGlobalSection
EndGlobal
</code></pre></div><p>This allows the .NET tooling to know that we’ve got some intentional organization going on in our code base. That these projects each form part of a bigger whole.</p>
<blockquote>
<p>It’s also nice to add a <a href="https://git-scm.com/docs/gitignore"><code>.gitignore</code></a> file for our new <code>VehicleQuotes.CreateUser</code> project to keep things manageable. <code>dotnet new</code> can help with that if we were to navigate into the <code>VehicleQuotes.CreateUser</code> directory and run:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet new gitignore
</code></pre></div></blockquote>
<blockquote>
<p>You can learn more about how to work with solutions via the .NET CLI in <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-sln">the official docs</a>.</p>
</blockquote>
<p>Now let’s modify our new project’s <code>.csproj</code> file so that it references the main web app project under <code>VehicleQuotes</code>. This will allow our console app to access all of the classes defined in the web app, as if it was a library or package.</p>
<p>If we move to the <code>VehicleQuotes.CreateUser</code> directory, we can do that with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet add reference ../VehicleQuotes/VehicleQuotes.csproj
</code></pre></div><p>The command itself is pretty self-explanatory. It just expects to be given the <code>.csproj</code> file of the project that we want to add as a reference in order to do its magic.</p>
<p>Running that should’ve added the following snippet to <code>VehicleQuotes.CreateUser/VehicleQuotes.CreateUser.csproj</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-xml" data-lang="xml"><span style="color:#b06;font-weight:bold"><ItemGroup></span>
<span style="color:#b06;font-weight:bold"><ProjectReference</span> <span style="color:#369">Include=</span><span style="color:#d20;background-color:#fff0f0">"..\VehicleQuotes\VehicleQuotes.csproj"</span> <span style="color:#b06;font-weight:bold">/></span>
<span style="color:#b06;font-weight:bold"></ItemGroup></span>
</code></pre></div><p>This way, .NET allows the code defined in the <code>VehicleQuotes</code> project to be used within the <code>VehicleQuotes.CreateUser</code> project.</p>
<blockquote>
<p>You can learn more about the add reference command in <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-add-reference">the official docs</a>.</p>
</blockquote>
<h3 id="setting-up-dependency-injection-in-the-console-app">Setting up dependency injection in the console app</h3>
<p>As a result of the previous steps, our new console app now has access to the classes defined within the web app. However, classes by themselves are no good if we can’t actually create instances of them that we can interact with. The premier method for making instances of classes available throughout a .NET application is via <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">dependency injection</a>. So, we need to set that up for our little console app.</p>
<p>Dependency injection is something that comes out of the box for ASP.NET Core web apps. Luckily for us, .NET makes it fairly easy to set it up in console apps as well by leveraging the same components.</p>
<p>For this app, we want to create user accounts. In the web app, user account management is done via <a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=netcore-cli">ASP.NET Core Identity</a>. Specifically, the <code>UserManager</code> class is used to create new user accounts. This console app will do the same.</p>
<blockquote>
<p>Take a look at <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/backend-task/VehicleQuotes/Controllers/UsersController.cs"><code>VehicleQuotes/Controllers/UsersController.cs</code></a> to see how the user accounts are created. If you’d like to know more about integrating ASP.NET Core Identity into an existing web app, I wrote <a href="https://www.endpointdev.com/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/">an article</a> about it.</p>
</blockquote>
<p>Before we do the dependency injection setup, let’s add a new class to our console app project that will encapsulate the logic of leveraging the <code>UserManager</code> for user account creation. This is the actual task that we want to perform. The new class will be defined in <code>VehicleQuotes.CreateUser/UserCreator.cs</code> and these will be its contents:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.CreateUser</span>;
<span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">UserCreator</span>
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> UserManager<IdentityUser> <span style="color:#00d;font-weight:bold">_</span>userManager;
<span style="color:#080;font-weight:bold">public</span> UserCreator(UserManager<IdentityUser> userManager) {
<span style="color:#00d;font-weight:bold">_</span>userManager = userManager;
}
<span style="color:#080;font-weight:bold">public</span> IdentityResult Run(<span style="color:#888;font-weight:bold">string</span> username, <span style="color:#888;font-weight:bold">string</span> email, <span style="color:#888;font-weight:bold">string</span> password)
{
<span style="color:#888;font-weight:bold">var</span> userCreateTask = <span style="color:#00d;font-weight:bold">_</span>userManager.CreateAsync(
<span style="color:#080;font-weight:bold">new</span> IdentityUser() { UserName = username, Email = email },
password
);
<span style="color:#888;font-weight:bold">var</span> result = userCreateTask.Result;
<span style="color:#080;font-weight:bold">return</span> result;
}
}
</code></pre></div><p>This class is pretty lean. All it does is define a constructor that expects an instance of <code>UserManager<IdentityUser></code>, which will be supplied via dependency injection; and a simple <code>Run</code> method that, when given a username, email, and password, asks the <code>UserManager<IdentityUser></code> instance that it was given to create a user account.</p>
<p>Moving on to setting up dependency injection now, we will do it in <code>VehicleQuotes.CreateUser/Program.cs</code>. Replace the contents of that file with this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.DependencyInjection</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.Hosting</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.CreateUser</span>;
IHost host = Host.CreateDefaultBuilder(args)
.UseContentRoot(System.AppContext.BaseDirectory)
.ConfigureServices((context, services) =>
{
<span style="color:#888;font-weight:bold">var</span> startup = <span style="color:#080;font-weight:bold">new</span> VehicleQuotes.Startup(context.Configuration);
startup.ConfigureServices(services);
services.AddTransient<UserCreator>();
})
.Build();
<span style="color:#888;font-weight:bold">var</span> userCreator = host.Services.GetRequiredService<UserCreator>();
userCreator.Run(args[<span style="color:#00d;font-weight:bold">0</span>], args[<span style="color:#00d;font-weight:bold">1</span>], args[<span style="color:#00d;font-weight:bold">2</span>]);
</code></pre></div><p>Let’s dissect this bit by bit.</p>
<p>First off, we’ve got a few <code>using</code> statements that we need in order to access some classes and extension methods that we need down below.</p>
<p>Next, we create and configure a new <code>IHost</code> instance. .NET Core introduced the concept of a “host” as an abstraction for programs; and packed in there a lot of functionality to help with things like configuration, logging and, most importantly for us, dependency injection. To put it simply, the simplest way of enabling dependency injection in a console app is to use a <code>Host</code> and all the goodies that come within.</p>
<blockquote>
<p>There’s much more information about hosts in <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host">.NET’s official documentation</a>.</p>
</blockquote>
<p><code>Host.CreateDefaultBuilder(args)</code> gives us an <code>IHostBuilder</code> instance that we can use to configure our host. In our case, we’ve chosen to call <code>UseContentRoot(System.AppContext.BaseDirectory)</code> on it, which makes it possible for the app to find assets (like <code>appconfig.json</code> files!) regardless of where its deployed and where its being called from.</p>
<p>This is important for us because, as you will see later, we will install this console app as a <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools">.NET Tool</a>. .NET Tools are installed in directories picked by .NET and can be run from anywhere in the system. So we need to make sure that our app can find its assets wherever it has been installed.</p>
<p>After that, we call <code>ConfigureServices</code> where we do a nice trick in order to make sure our console app has all the same configuration as the web app as far as dependency injection goes.</p>
<p>You see, in ASP.NET Core, all the service classes that are to be made available to the application via dependency injection are configured within the web app’s <code>Startup</code> class' <code>ConfigureServices</code> method. <code>VehicleQuotes</code> is no exception. So, in order for our console app to have access to all of the services (i.e. instances of classes) that the web app does, the console app needs to call that same code. And that’s exactly what’s happening in these two lines:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888;font-weight:bold">var</span> startup = <span style="color:#080;font-weight:bold">new</span> VehicleQuotes.Startup(context.Configuration);
startup.ConfigureServices(services);
</code></pre></div><p>We create a new instance of the web app’s <code>Startup</code> class and call its <code>ConfigureServices</code> method. That’s the key element that allows the console app to have access to all the logic that the web app does. Including the services/classes provided by ASP.NET Core Identity like <code>UserManager<IdentityUser></code>, which <code>UserCreator</code> needs in order to function.</p>
<p>Once that’s done, the rest is straightforward.</p>
<p>We also add our new <code>UserCreator</code> to the dependency injection engine via:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services.AddTransient<UserCreator>();
</code></pre></div><blockquote>
<p>Curious about what <code>Transient</code> means? <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes">The official .NET documentation</a> has the answer.</p>
</blockquote>
<p>And that allows us to obtain an instance of it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888;font-weight:bold">var</span> userCreator = host.Services.GetRequiredService<UserCreator>();
</code></pre></div><p>And then, it’s just a matter of calling its <code>Run</code> method like so, passing it the command-line arguments:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">userCreator.Run(args[<span style="color:#00d;font-weight:bold">0</span>], args[<span style="color:#00d;font-weight:bold">1</span>], args[<span style="color:#00d;font-weight:bold">2</span>]);
</code></pre></div><p><code>args</code> is a special variable that contains an array with the arguments given by command line. That means that our console app can be called like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet run test_username test_email@email.com mysecretpassword
</code></pre></div><p>Go ahead, you can try it out and see the app log what it’s doing. Once done, it will also have created a new record in the database.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ psql -h localhost -U vehicle_quotes
psql (14.3 (Ubuntu 14.3-0ubuntu0.22.04.1), server 14.2 (Debian 14.2-1.pgdg110+1))
Type "help" for help.
vehicle_quotes=# select user_name, email from public."AspNetUsers";
user_name | email
---------------+----------------------------
test_username | test_email@email.com
(1 rows)
</code></pre></div><p>Pretty neat, huh? At this point we have a console app that creates user accounts for our existing web app. It works, but it could be better. Let’s add a nice command-line interface experience now.</p>
<h3 id="improving-the-cli-with-commandlineparser">Improving the CLI with CommandLineParser</h3>
<p>With help from <a href="https://github.com/commandlineparser/commandline">CommandLineParser</a>, we can develop a Unix-like command-line interface for our app. We can use it to add help text, examples, have strongly typed parameters and useful error messages when said parameters are not correctly provided. Let’s do that now.</p>
<p>First, we need to install the package in our console app project by running the following command from within the project’s directory (<code>VehicleQuotes.CreateUser</code>):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet add package CommandLineParser
</code></pre></div><p>After that’s done, a new section will have been added to <code>VehicleQuotes.CreateUser/VehicleQuotes.CreateUser.csproj</code> that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-xml" data-lang="xml"><span style="color:#b06;font-weight:bold"><ItemGroup></span>
<span style="color:#b06;font-weight:bold"><PackageReference</span> <span style="color:#369">Include=</span><span style="color:#d20;background-color:#fff0f0">"CommandLineParser"</span> <span style="color:#369">Version=</span><span style="color:#d20;background-color:#fff0f0">"2.9.1"</span> <span style="color:#b06;font-weight:bold">/></span>
<span style="color:#b06;font-weight:bold"></ItemGroup></span>
</code></pre></div><p>Now our console app can use the classes provided by the package.</p>
<p>All specifications for <code>CommandLineParser</code> are done via a plain old C# class that we need to define. For this console app, which accepts three mandatory arguments, such a class could look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">CommandLine</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">CommandLine.Text</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.CreateUser</span>;
<span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">CliOptions</span>
{
<span style="color:#369"> [Value(0, Required = true, MetaName = "username", HelpText = "The username of the new user account to create.")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Username { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [Value(1, Required = true, MetaName = "email", HelpText = "The email of the new user account to create.")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Email { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [Value(2, Required = true, MetaName = "password", HelpText = "The password of the new user account to create.")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Password { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [Usage(ApplicationAlias = "create_user")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">static</span> IEnumerable<Example> Examples
{
<span style="color:#080;font-weight:bold">get</span>
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> List<Example> {
<span style="color:#080;font-weight:bold">new</span> (
<span style="color:#d20;background-color:#fff0f0">"Create a new user account"</span>,
<span style="color:#080;font-weight:bold">new</span> CliOptions { Username = <span style="color:#d20;background-color:#fff0f0">"name"</span>, Email = <span style="color:#d20;background-color:#fff0f0">"email@domain.com"</span>, Password = <span style="color:#d20;background-color:#fff0f0">"secret"</span> }
)
};
}
}
}
</code></pre></div><p>I’ve decided to name it <code>CliOptions</code> but really, it could have been anything. Go ahead and create it in <code>VehicleQuotes.CreateUser/CliOptions.cs</code>. There are a few interesting elements to note here.</p>
<p>The key aspect is that we have a few properties: <code>Username</code>, <code>Email</code>, and <code>Password</code>. These represent our three command-line arguments. Thanks to the <code>Value</code> <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/">attributes</a> that they have been annotated with, <code>CommandLineParser</code> will know that that’s their purpose. You can see how the attributes themselves also contain each argument’s specification like the order in which they should be supplied, as well as their name and help text.</p>
<p>This class also defines an <code>Examples</code> getter which is used by <code>CommandLineParser</code> to print out usage examples into the console when our app’s help option is invoked.</p>
<p>Other than that, the class itself is unremarkable. In summary, it’s a number of fields annotated with attributes so that <code>CommandLineParser</code> knows what to do with it.</p>
<p>In order to actually put it to work, we update our <code>VehicleQuotes.CreateUser/Program.cs</code> like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
<span style="color:#000;background-color:#dfd">+using CommandLine;
</span><span style="color:#000;background-color:#dfd"></span> using VehicleQuotes.CreateUser;
<span style="color:#000;background-color:#dfd">+void Run(CliOptions options)
</span><span style="color:#000;background-color:#dfd">+{
</span><span style="color:#000;background-color:#dfd"></span> IHost host = Host.CreateDefaultBuilder(args)
.UseContentRoot(System.AppContext.BaseDirectory)
.ConfigureServices((context, services) =>
{
var startup = new VehicleQuotes.Startup(context.Configuration);
startup.ConfigureServices(services);
services.AddTransient<UserCreator>();
})
.Build();
var userCreator = host.Services.GetRequiredService<UserCreator>();
<span style="color:#000;background-color:#fdd">- userCreator.Run(args[0], args[1], args[2]);
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ userCreator.Run(options.Username, options.Email, options.Password);
</span><span style="color:#000;background-color:#dfd">+}
</span><span style="color:#000;background-color:#dfd">+
</span><span style="color:#000;background-color:#dfd">+Parser.Default
</span><span style="color:#000;background-color:#dfd">+ .ParseArguments<CliOptions>(args)
</span><span style="color:#000;background-color:#dfd">+ .WithParsed(options => Run(options));
</span></code></pre></div><p>We’ve wrapped <code>Program.cs</code>’s original code into a method simply called <code>Run</code>.</p>
<p>Also, we’ve added this snippet at the bottom of the file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">Parser.Default
.ParseArguments<CliOptions>(args)
.WithParsed(options => Run(options));
</code></pre></div><p>That’s how we ask <code>CommandLineParser</code> to parse the incoming CLI arguments, as specified by <code>CliOptions</code> and, if it can be done successfully, then execute the rest of the program by calling the <code>Run</code> method.</p>
<p>Neatly, we also no longer have to use the <code>args</code> array directly in order to get the command-line arguments provided to the app, instead we use the <code>options</code> object that <code>CommandLineParser</code> creates for us once it has done its parsing. You can see it in this line:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">userCreator.Run(options.Username, options.Email, options.Password);
</code></pre></div><p><code>options</code> is an instance of our very own <code>CliOptions</code> class, so we can access the properties that we defined within it. These contain the arguments that were passed to the program.</p>
<p>If you were to try <code>dotnet run</code> right now, you’d see the following output:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">VehicleQuotes.CreateUser 1.0.0
Copyright (C) 2022 VehicleQuotes.CreateUser
ERROR(S):
A required value not bound to option name is missing.
USAGE:
Create a new user account:
create_user name email@domain.com secret
--help Display this help screen.
--version Display version information.
username (pos. 0) Required. The username of the new user account to create.
email (pos. 1) Required. The email of the new user account to create.
password (pos. 2) Required. The password of the new user account to create.
</code></pre></div><p>As you can see, <code>CommandLineParser</code> detected that no arguments were given, and as such, it printed out an error message, along with the descriptions, help text and example that we defined. Basically the instructions on how to use our console app.</p>
<h3 id="deploying-the-console-app-as-a-net-tool">Deploying the console app as a .NET tool</h3>
<p>OK, we now have a console app that does what it needs to do, with a decent interface. The final step is to make it even more accessible by deploying it as a .NET tool. If we do that, we’d be able to invoke it with a command like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet create_user Kevin kevin@gmail.com secretpw
</code></pre></div><p>.NET makes this easy for us. There’s a caveat that we’ll discuss later, but for now, let’s go through the basic setup.</p>
<p>.NET tools are essentially just glorified NuGet packages. As such, we begin by adding some additional package-related configuration options to <code>VehicleQuotes.CreateUser/VehicleQuotes.CreateUser.csproj</code>. We add them as children elements to the <code><PropertyGroup></code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-xml" data-lang="xml"><span style="color:#b06;font-weight:bold"><PackAsTool></span>true<span style="color:#b06;font-weight:bold"></PackAsTool></span>
<span style="color:#b06;font-weight:bold"><PackageOutputPath></span>./nupkg<span style="color:#b06;font-weight:bold"></PackageOutputPath></span>
<span style="color:#b06;font-weight:bold"><ToolCommandName></span>create_user<span style="color:#b06;font-weight:bold"></ToolCommandName></span>
<span style="color:#b06;font-weight:bold"><VersionPrefix></span>1.0.0<span style="color:#b06;font-weight:bold"></VersionPrefix></span>
</code></pre></div><p>With that, we signal .NET that we want the console app to be packed as a tool, the path where it should put the package itself, and what its name will be. That is, how will it be invoked via the console (remember we want to be able to do <code>dotnet create_user</code>).</p>
<p>Finally, we specify a version number. When dealing with NuGet packages, versioning them is very important, as that drives caching and downloading logic in NuGet. More on that later when we talk about the aforementioned caveats.</p>
<p>Now, to build the package, we use:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet pack
</code></pre></div><p>That will build the application and produce a <code>VehicleQuotes.CreateUser/nupkg/VehicleQuotes.CreateUser.1.0.0.nupkg</code> file.</p>
<p>We won’t make the tool available for the entire system. Instead, we will make it available from within our solution’s directory only. We can make that happen if we create a tool manifest file in the source code’s root directory. That’s done with this command, run from the root directory:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet new tool-manifest
</code></pre></div><p>That should create a new file: <code>.config/dotnet-tools.json</code>.</p>
<p>Now, also from the root directory, we can finally install our tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet tool install --add-source ./VehicleQuotes.CreateUser/nupkg VehicleQuotes.CreateUser
</code></pre></div><p>This is the regular command to install any tools in .NET. The interesting part is that we use the <code>--add-source</code> option to point it to the path where our freshly built package is located.</p>
<p>After that, .NET shows this output:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ dotnet tool install --add-source ./VehicleQuotes.CreateUser/nupkg VehicleQuotes.CreateUser
You can invoke the tool from this directory using the following commands: 'dotnet tool run create_user' or 'dotnet create_user'.
Tool 'vehiclequotes.createuser' (version '1.0.0') was successfully installed. Entry is added to the manifest file /path/to/solution/.config/dotnet-tools.json.
</code></pre></div><p>It tells us all we need to know. Check out the <code>.config/dotnet-tools.json</code> to see how the tool has been added there. All this means that now we can run our console app as a .NET tool:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ dotnet create_user --help
VehicleQuotes.CreateUser 1.0.0
Copyright (C) 2022 VehicleQuotes.CreateUser
USAGE:
Create a new user account:
create_user name email@domain.com secret
--help Display this help screen.
--version Display version information.
username (pos. 0) Required. The username of the new user account to create.
email (pos. 1) Required. The email of the new user account to create.
password (pos. 2) Required. The password of the new user account to create.
</code></pre></div><p>Pretty sweet, huh? And yes, it has taken a lot more effort than what it would’ve taken in Ruby on Rails, but hey, the end result is pretty fabulous I think, and we learned a new thing. Besides, once you’ve done it once, the skeleton can be easily reused for all kinds of different backend tasks.</p>
<p>Now, before we wrap this up, there’s something we need to consider when actively developing these tools. That is, when making changes and re-installing constantly.</p>
<p>The main aspect to understand is that tools are just NuGet packages, and as such are beholden to the NuGet package infrastructure. Which includes caching. If you’re in the process of developing your tool and are quickly making and deploying changes, NuGet won’t update the cache unless you do one of two things:</p>
<ol>
<li>Manually clear it with a command like <code>dotnet nuget locals all --clear</code>.</li>
<li>Bump up the version of the tool by updating the value of <code><VersionSuffix></code> in the project (<code>.csproj</code>) file.</li>
</ol>
<p>This means that, unless you do one these, the changes that you make to the app between re-builds (with <code>dotnet pack</code>) and re-installs (with <code>dotnet dotnet tool install</code>) won’t ever make their way to the package that’s actually installed in your system. So be sure to keep that in mind.</p>
<h3 id="table-of-contents">Table of contents</h3>
<ul>
<li><a href="#main-content">Implementing Backend Tasks in ASP.NET Core</a></li>
<li><a href="#what-we-want-to-accomplish">What we want to accomplish</a></li>
<li><a href="#creating-a-new-console-app-that-references-the-existing-web-app-as-a-library">Creating a new console app that references the existing web app as a library</a></li>
<li><a href="#setting-up-dependency-injection-in-the-console-app">Setting up dependency injection in the console app</a></li>
<li><a href="#improving-the-cli-with-commandlineparser">Improving the CLI with CommandLineParser</a></li>
<li><a href="#deploying-the-console-app-as-a-net-tool">Deploying the console app as a .NET tool</a></li>
</ul>
Implementing Authentication in ASP.NET Core Web APIshttps://www.endpointdev.com/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/2022-06-17T00:00:00+00:00Kevin Campusano
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/stockholm-buildings.webp" alt="Several buildings with a lightly cloudy blue sky"></p>
<!-- Photo by Seth Jensen -->
<p>Authentication is a complex space. There are many problem scenarios and many more solutions. When it comes to <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0&tabs=visual-studio-code">Web APIs</a> written with <a href="https://github.com/dotnet/aspnetcore">ASP.NET Core</a>, there are various fully featured options like <a href="https://duendesoftware.com/">Duende IdentityServer</a> or <a href="https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad">Azure Active Directory</a>. These promise to be “everything but the kitchen sink” solutions which are robust and allow you to deal with many complex requirements.</p>
<p>But what if our requirements dictate that we need something simpler? Do we have to roll out our own from scratch? Or does ASP.NET Core offer smaller, customizable, somewhat independent puzzle pieces that we can put together without having to write all the code ourselves and still have a good amount of control?</p>
<p>Spoiler alert: The answer to that last question is yes. And we’re going to talk about it in this very article.</p>
<blockquote>
<p>There is a <a href="#table-of-contents">Table of contents</a> at the end of this post.</p>
</blockquote>
<h3 id="two-approaches-to-authentication-jwt-and-api-keys">Two approaches to authentication: JWT and API Keys</h3>
<p>In this article, we’ll take an existing ASP.NET Core Web API and add authentication capabilities to it. Specifically, we’ll support two authentication schemes commonly used for Web APIs: JWT and API Keys. Also, we will use our own database for storage of user accounts and credentials.</p>
<p>The project that we will work with is a simple ASP.NET Web API backed by a <a href="https://www.postgresql.org/">Postgres</a> database. It has a few endpoints for <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>ing automotive related data and for calculating values of vehicles based on various aspects of them. You can read all about the process of building it <a href="https://www.endpointdev.com/blog/2021/07/dotnet-5-web-api/">here</a>. I also added a few database integration tests for it <a href="https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/">here</a>.</p>
<p>You can find it <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api">on GitHub</a>. If you’d like to follow along, clone the repository and checkout this commit: <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/cd290c765fcd2c6693008d3dc76fa931098dcaa0">cd290c765fcd2c6693008d3dc76fa931098dcaa0</a>. It represents the project as it was before applying all the changes from this article.</p>
<p>You can follow the instructions in <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/json-web-token/VehicleQuotes/README.md">the project’s README file</a> in order to get the app up and running.</p>
<h3 id="managing-user-accounts-with-aspnet-core-identity">Managing user accounts with ASP.NET Core Identity</h3>
<p>Let’s deal first with the requirement of storing the user accounts in our own database.</p>
<p>Luckily for us, ASP.NET Core provides us with <a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=netcore-cli">Identity</a>. This is an API that offers a comprehensive solution for authentication. It can connect with the aforementioned Duende
IdentityServer for <a href="https://openid.net/connect/">OpenID Connect</a> and <a href="https://oauth.net/2/">OAuth 2.0</a>, supports authentication via third parties like Facebook or Google, and can fully integrate with user interfaces on web apps, complete with scaffolding/autogeneration capabilities too.</p>
<p>Most importantly for us, it supports management of user accounts stored in our own database. It includes data for third party logins, passwords, roles for authorization, email confirmation, access tokens, etc.</p>
<p>That’s a lot of of functionality baked in there. But we don’t need all that, so let’s see how we can take only the bits and pieces that we need in order to fulfill our specific requirements.</p>
<p>Specifically, we want:</p>
<ol>
<li>Tables in our database for storage of user accounts and passwords.</li>
<li>A programmatic way to create, fetch and validate users using those tables.</li>
</ol>
<p>It’s actually pretty simple.</p>
<h4 id="install-the-necessary-nuget-packages">Install the necessary NuGet packages</h4>
<p>First, we need to install a couple of <a href="https://www.nuget.org/">NuGet</a> packages:</p>
<ul>
<li><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Identity/">Microsoft.AspNetCore.Identity</a> which contains the core library.</li>
<li><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Identity.EntityFrameworkCore">Microsoft.AspNetCore.Identity.EntityFrameworkCore</a> which includes the classes that Identity needs in order to properly interact with <a href="https://docs.microsoft.com/en-us/ef/">Entity Framework</a>, which is what we’re using in our Web API for interfacing with the database.</li>
</ul>
<p>We can install them by running these two commands from the <code>VehicleQuotes</code> directory:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ dotnet add package Microsoft.AspNetCore.Identity
$ dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
</code></pre></div><p>That will add the following lines to the <code>VehicleQuotes/VehicleQuotes.csproj</code> project file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> <Project Sdk="Microsoft.NET.Sdk.Web">
...
<ItemGroup>
...
<span style="color:#000;background-color:#dfd">+ <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
</span><span style="color:#000;background-color:#dfd">+ <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.5" />
</span><span style="color:#000;background-color:#dfd"></span> ...
</ItemGroup>
</Project>
</code></pre></div><h4 id="update-the-dbcontext-to-include-the-identity-tables">Update the DbContext to include the Identity tables</h4>
<p>Next step is to configure our <code>DbContext</code> class so that it includes the new tables that we need from the Identity library. So let’s go to <code>VehicleQuotes/Data/VehicleQuotesContext.cs</code> and update it to do so.</p>
<p>We need to include these new <code>using</code> statements at the top of the file so that we have access to the classes that we need from the NuGet packages we just installed:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity.EntityFrameworkCore</span>;
</code></pre></div><p>Next, instead of <code>DbContext</code>, the <code>VehicleQuotesContext</code> class should inherit from <code>IdentityUserContext<IdentityUser></code>.</p>
<p><code>IdentityUserContext</code> is a class provided by ASP.NET Core Identity that’s designed so that our <code>DbContext</code> can inherit from it and gain user management functionality. Namely, it includes a new <code>DbSet</code> (and consequently, a table) for holding user accounts (aptly named <code>Users</code>), among other things that we won’t need.</p>
<blockquote>
<p><code>IdentityUserContext</code> has a more feature rich counterpart called <code>IdentityDbContext</code>, which also includes <code>DbSet</code>s to support roles based authorization. We don’t need all that so we use its simpler cousin. Feel free to explore the <a href="https://github.com/dotnet/aspnetcore/blob/main/src/Identity/EntityFrameworkCore/src/IdentityDbContext.cs">source code on GitHub</a> to see all it offers.</p>
</blockquote>
<p>The <a href="https://docs.microsoft.com/en-us/dotnet/standard/generics/">generic type parameter</a> that we give it, <code>IdentityUser</code>, is a class that’s also provided by the Identity library. Its purpose is to serve as a default <a href="https://docs.microsoft.com/en-us/ef/core/modeling/entity-types?tabs=data-annotations">Entity Type</a> for our user model and, as a consequence, our <code>Users</code> <code>DbSet</code>.</p>
<p>In summary, by having our <code>DbContext</code> class inherit from <code>IdentityUserContext<IdentityUser></code>, we’re telling Identity that we want it to augment our <code>DbContext</code> (and database) to include the core user management tables and that we want our users table to have the same columns as <code>IdentityUser</code>.</p>
<blockquote>
<p>If we wanted to include more columns in our users table, what we would have to do is create a new class, make it inherit from <code>IdentityUser</code>, define any additional fields that we want on it, and use that class as a type parameter to <code>IdentityUserContext</code>. For us for now, the default works just fine. You can learn more about customizing Identity in <a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-6.0">the official docs</a>.</p>
</blockquote>
<p>The change looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#000;background-color:#fdd">-public class VehicleQuotesContext : DbContext
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+public class VehicleQuotesContext : IdentityUserContext<IdentityUser>
</span></code></pre></div><p>Finally, <code>IdentityUserContext</code> has some logic that it needs to run when it is being created. In order to allow it to run that logic, let’s add the following line to our <code>VehicleQuotesContext</code>’s <code>OnModelCreating</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> protected override void OnModelCreating(ModelBuilder modelBuilder)
{
<span style="color:#000;background-color:#dfd">+ base.OnModelCreating(modelBuilder);
</span><span style="color:#000;background-color:#dfd"></span> // ...
}
</code></pre></div><p>This calls <code>IdentityUserContext</code>’s own <code>OnModelCreating</code> implementation so that it can set itself up properly.</p>
<p>The complete file should be looking like this now:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">VehicleQuotesContext</span> : IdentityUserContext<IdentityUser>
{
<span style="color:#080;font-weight:bold">public</span> VehicleQuotesContext (DbContextOptions<VehicleQuotesContext> options)
: <span style="color:#080;font-weight:bold">base</span>(options)
{
}
<span style="color:#080;font-weight:bold">public</span> DbSet<Make> Makes { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<Size> Sizes { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<BodyType> BodyTypes { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<Model> Models { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<ModelStyle> ModelStyles { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<ModelStyleYear> ModelStyleYears { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<QuoteRule> QuoteRules { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<QuoteOverride> QuoteOverides { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DbSet<Quote> Quotes { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">void</span> OnModelCreating(ModelBuilder modelBuilder)
{
<span style="color:#080;font-weight:bold">base</span>.OnModelCreating(modelBuilder);
modelBuilder.Entity<Size>().HasData(
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">1</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Subcompact"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">2</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Compact"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">3</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Mid Size"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">5</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Full Size"</span> }
);
modelBuilder.Entity<BodyType>().HasData(
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">1</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Coupe"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">2</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Sedan"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">3</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Hatchback"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">4</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Wagon"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">5</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Convertible"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">6</span>, Name = <span style="color:#d20;background-color:#fff0f0">"SUV"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">7</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Truck"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">8</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Mini Van"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">9</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Roadster"</span> }
);
}
}
}
</code></pre></div><h4 id="applying-database-changes">Applying database changes</h4>
<p>The <code>Users</code> <code>DbSet</code> that we added into our data model by inheriting from <code>IdentityUserContext<IdentityUser></code> will become a table once we create and apply a database migration. That’s what we will do next.</p>
<p>That’s simple in ASP.NET Core. All we need to do is run a command like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet ef migrations add AddIdentityTables
</code></pre></div><p>That should produce a new migration file named something like <code>20220605003253_AddIdentityTables.cs</code>. If you explore it, you’ll see how it contains the definitions for a few new database tables. Including the one that we want: <code>AspNetUsers</code>. That’s the one that we will use to store our user account records.</p>
<p>Next, we apply the migration with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet ef database update
</code></pre></div><p>If you now connect to the database, you’ll see the new tables in there.</p>
<p>If you have the database running in a Docker container like we discussed in the beginning of the article, you should be able to connect to it with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ psql -h localhost -U vehicle_quotes
</code></pre></div><p>Then, to see the tables:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">vehicle_quotes=# \dt
List of relations
Schema | Name | Type | Owner
--------+-----------------------+-------+----------------
public | AspNetUserClaims | table | vehicle_quotes
public | AspNetUserLogins | table | vehicle_quotes
public | AspNetUserTokens | table | vehicle_quotes
public | AspNetUsers | table | vehicle_quotes
...
(14 rows)
</code></pre></div><p>And that’s pretty much it when it comes to having a sensible storage for user accounts. That was pretty inexpensive, wasn’t it? All we had to do was install some NuGet packages, tweak our existing DbContext, and run some migrations.</p>
<p>The best thing is that we’re not done yet. We can also take advantage of ASP.NET Core Identity to manage users programmatically. Instead of interacting with these tables directly, we will use the service classes provided by Identity to create new users, fetch existing ones and validate their credentials.</p>
<p>Before we can do that though, we must first do some configuration in our app’s <code>Startup</code> class so that said services are properly set up to our liking and are made available to our application.</p>
<!-- Next step for us is creating and fetching users. Let's see how we can make that happen. -->
<h4 id="configuring-the-identity-services">Configuring the Identity services</h4>
<p>We need to add some code to <code>VehicleQuotes/Startup.cs</code>. With it, we configure the Identity system and add a few service classes to ASP.NET Core’s <a href="https://en.wikipedia.org/wiki/Inversion_of_control">IoC</a> <a href="https://www.tutorialsteacher.com/ioc/ioc-container">container</a> so that they are available to our app via <a href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a>.</p>
<p>We need a new <code>using</code> statement:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
</code></pre></div><p>And the following code added to the <code>ConfigureServices</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services
.AddIdentityCore<IdentityUser>(options => {
options.SignIn.RequireConfirmedAccount = <span style="color:#080;font-weight:bold">false</span>;
options.User.RequireUniqueEmail = <span style="color:#080;font-weight:bold">true</span>;
options.Password.RequireDigit = <span style="color:#080;font-weight:bold">false</span>;
options.Password.RequiredLength = <span style="color:#00d;font-weight:bold">6</span>;
options.Password.RequireNonAlphanumeric = <span style="color:#080;font-weight:bold">false</span>;
options.Password.RequireUppercase = <span style="color:#080;font-weight:bold">false</span>;
options.Password.RequireLowercase = <span style="color:#080;font-weight:bold">false</span>;
})
.AddEntityFrameworkStores<VehicleQuotesContext>();
</code></pre></div><p>The call to <code>AddIdentityCore</code> makes several Identity utility classes available to the application. Among those, <code>UserManager</code> is the only one we will use. We will use it later to… well, manage users. You can also see how we’ve set a few options related to how the user accounts are handled. <code>options.SignIn.RequireConfirmedAccount</code> controls whether new accounts need to be confirmed via email before they are available. With <code>options.User.RequireUniqueEmail</code>, we tell Identity to enforce uniqueness of emails on user accounts. And finally the <code>options.Password.*</code> options configure the password strength requirements.</p>
<blockquote>
<p>You can explore all the available options in <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-6.0">the official docs</a>.</p>
</blockquote>
<p>Then, the call to <code>AddEntityFrameworkStores</code> tells the Identity system that it should use our <code>VehicleQuotesContext</code> for data storage.</p>
<h4 id="creating-users">Creating users</h4>
<p>With that configuration out of the way, we can now write some code to create new user accounts. To keep things simple, we’ll add a new <code>UsersController</code> to our project that will expose a new endpoint that offers that functionality.</p>
<p>Let’s start with this in <code>VehicleQuotes/Controllers/UsersController.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Mvc</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Controllers</span>
{
<span style="color:#369"> [Route("api/[controller]</span><span style="color:#d20;background-color:#fff0f0">")]
</span><span style="color:#d20;background-color:#fff0f0"></span><span style="color:#369"> [ApiController]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">UsersController</span> : ControllerBase
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> UserManager<IdentityUser> <span style="color:#00d;font-weight:bold">_</span>userManager;
<span style="color:#080;font-weight:bold">public</span> UsersController(
UserManager<IdentityUser> userManager
) {
<span style="color:#00d;font-weight:bold">_</span>userManager = userManager;
}
}
}
</code></pre></div><p>As you can see, this controller defines a dependency on <code>UserManager<IdentityUser></code>, an instance of which is injected via the constructor. This is one of the classes made available to us when we configured the Identity core services in our app’s <code>Startup</code>. We will use it to create new user records.</p>
<p>Before that though, we need to define a new class that encapsulates the payload for a request to our endpoint. When it comes to creating user accounts, all we need is a username, a password, and an email. As such, we add the following class in a new <code>VehicleQuotes/ResourceModels/User.cs</code> file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">User</span>
{
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> UserName { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Password { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Email { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Now, we shall use this type as the parameter for a new <code>PostUser</code> action method in the <code>UserController</code> which will expose the new user account creation endpoint in our API. The method looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888">// POST: api/Users
</span><span style="color:#888"></span><span style="color:#369">[HttpPost]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<User>> PostUser(User user)
{
<span style="color:#080;font-weight:bold">if</span> (!ModelState.IsValid)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(ModelState);
}
<span style="color:#888;font-weight:bold">var</span> result = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.CreateAsync(
<span style="color:#080;font-weight:bold">new</span> IdentityUser() { UserName = user.UserName, Email = user.Email },
user.Password
);
<span style="color:#080;font-weight:bold">if</span> (!result.Succeeded)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(result.Errors);
}
user.Password = <span style="color:#080;font-weight:bold">null</span>;
<span style="color:#080;font-weight:bold">return</span> Created(<span style="color:#d20;background-color:#fff0f0">""</span>, user);
}
</code></pre></div><p>Be sure to also add the following <code>using</code> statements:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Threading.Tasks</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>;
</code></pre></div><p><code>System.Threading.Tasks</code> allows us to reference the class <code>Task<></code> which is the return type of the new <code>PostUser</code> method. <code>VehicleQuotes.ResourceModels</code> is where our new <code>User</code> class lives.</p>
<p>Thanks to the instance of <code>UserManager<IdentityUser></code> that we’re holding onto, this method is very straightforward. The most interesting portion is this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888;font-weight:bold">var</span> result = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.CreateAsync(
<span style="color:#080;font-weight:bold">new</span> IdentityUser() { UserName = user.UserName, Email = user.Email },
user.Password
);
</code></pre></div><p>Here we’re <code>new</code>ing up an <code>IdentityUser</code> instance using the given request parameters and passing it on to <code>UserManager<IdentityUser></code>’s <code>CreateAsync</code> method, along with the password that was also given in the incoming request. This puts the Identity system to work for us and properly create a new user account.</p>
<p>Then, we can inspect its return value (which we capture in the <code>result</code> variable) to determine if the operation was successful. That way we can respond appropriately to our API’s caller.</p>
<p>Finally, with…</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">user.Password = <span style="color:#080;font-weight:bold">null</span>;
<span style="color:#080;font-weight:bold">return</span> Created(<span style="color:#d20;background-color:#fff0f0">""</span>, user);
</code></pre></div><p>we return the data representing the newly created user account but we’re discreet and make sure not to include the password.</p>
<p>With that, we can test our API. Fire it up with <code>dotnet run</code> (or <a href="https://docs.microsoft.com/en-us/aspnet/core/test/hot-reload?view=aspnetcore-6.0"><code>dotnet watch</code></a>!) and send a <code>POST</code> request to our new endpoint at <code>http://0.0.0.0:5000/api/Users</code> with a payload like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"userName"</span>: <span style="color:#d20;background-color:#fff0f0">"newuser000"</span>,
<span style="color:#b06;font-weight:bold">"email"</span>: <span style="color:#d20;background-color:#fff0f0">"newuser000@endpointdev.com"</span>,
<span style="color:#b06;font-weight:bold">"password"</span>: <span style="color:#d20;background-color:#fff0f0">"password"</span>
}
</code></pre></div><p>I just tried it in <a href="https://www.postman.com/">Postman</a> and this is what it looked like:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-user.webp" alt="Successful user creation request in Postman"></p>
<p>Feel free to test it out further. Try repeated emails or usernames. Try passwords that don’t meet the criteria we defined when configuring Identity in the app’s <code>Startup</code> class. It all works as you’d expect.</p>
<p>You can inspect the database and see the newly created record too:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">vehicle_quotes=# select id, user_name, email, password_hash from "AspNetUsers";
-[ RECORD 1 ]-+--------------------------------------
id | aaad07b4-f109-4255-8caa-185fd7694c72
user_name | newuser000
email | newuser000@endpointdev.com
password_hash | AQAAAAEAACcQAAAAEJJTV7M2Ejqd3K3iC...
</code></pre></div><p>And there it is, that’s our record. With a hashed password and everything.</p>
<p>Now let’s see what would an endpoint to fetch users look like:</p>
<h4 id="fetching-users">Fetching users</h4>
<p><code>UserManager<IdentityUser></code> offers a <code>FindByNameAsync</code> method that we can use to retrieve users by name. We can add a new endpoint that leverages it like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888">// GET: api/Users/username
</span><span style="color:#888"></span><span style="color:#369">[HttpGet("{username}")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<User>> GetUser(<span style="color:#888;font-weight:bold">string</span> username)
{
IdentityUser user = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.FindByNameAsync(username);
<span style="color:#080;font-weight:bold">if</span> (user == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> User
{
UserName = user.UserName,
Email = user.Email
};
}
</code></pre></div><p>This is even more straightforward than the previous one. We just call the <code>FindByNameAsync</code> method by giving it the username of the account we want, make sure that we actually found it, and then return the data.</p>
<p>For the response, we use the same <code>User</code> class that we created to represent the input for the creation endpoint. If we added more fields to the user profile, we could include them here. Alas, we only have username and email for now.</p>
<p>Restart the app and we can now make a request like this:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/get-user.webp" alt="Successful user fetch request in Postman"></p>
<p>Pretty neat, huh? Try a username that does not exist and you should see the API respond with a 404.</p>
<blockquote>
<p>One quick improvement that we can do before we move on: let’s change <code>PostUser</code>’s return statement to this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">return</span> CreatedAtAction(<span style="color:#d20;background-color:#fff0f0">"GetUser"</span>, <span style="color:#080;font-weight:bold">new</span> { username = user.UserName }, user);
</code></pre></div><p>All that does is include a <code>Location</code> header on the POST Users endpoint’s response that contains the URL for the newly created user. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201">That’s just being a good citizen</a>.</p>
</blockquote>
<h3 id="implementing-jwt-bearer-token-authentication">Implementing JWT Bearer Token authentication</h3>
<p>Now that we have the user management capabilities that we need, let’s implement some actual authentication. We will start by adding <a href="https://jwt.io/">JWT</a>-based authentication to our API.</p>
<p>The strategy that we will use is to create a new API endpoint that clients can <code>POST</code> credentials to and that will respond to them with a fresh, short-lived token. They will then be able to use that token for subsequent requests by including it via headers.</p>
<p>We will pick a random endpoint to secure, just to serve as an example. That is, an endpoint that requires authentication in order to be accessed. <code>GET api/BodyTypes</code> is a good candidate. It is defined in <code>VehicleQuotes/Controllers/BodyTypesController.cs</code>’s <code>GetBodyTypes</code> action method. Feel free to test it out.</p>
<h4 id="creating-tokens">Creating tokens</h4>
<p>Let’s start by creating the new endpoint that clients will call to obtain the auth tokens. In order to do so, we need a few things:</p>
<ol>
<li>A class that represents incoming request data.</li>
<li>A class that represents outgoing response data.</li>
<li>A class that can generate tokens for a given user.</li>
<li>A new action method in <code>UsersController</code> that does the work.</li>
</ol>
<h5 id="the-request-data-structure">The request data structure</h5>
<p>To deal with step one, let’s define a new class in <code>VehicleQuotes/ResourceModels/AuthenticationRequest.cs</code> that looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">AuthenticationRequest</span>
{
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> UserName { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Password { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>All users need to authenticate is provide a set of credentials: username and password. So we have this class that contains fields for both and that will be used as input for our endpoint.</p>
<h5 id="the-response-data-structure">The response data structure</h5>
<p>Next, we need to define a class to represent that endpoint’s response. I’ve added the following class in <code>VehicleQuotes/ResourceModels/AuthenticationResponse.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">AuthenticationResponse</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Token { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DateTime Expiration { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Just a simple data structure containing the token itself and a date letting clients know when they can expect it to expire.</p>
<h5 id="the-class-that-creates-jwts">The class that creates JWTs</h5>
<p>Step 3 is creating a class that can produce the tokens. This is the most interesting part in terms of complexity. Before we can do that though, let’s add the following configurations to <code>VehicleQuotes/appsettings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json"> <span style="color:#d20;background-color:#fff0f0">"Jwt"</span><span style="color:#a61717;background-color:#e3d2d2">:</span> {
<span style="color:#b06;font-weight:bold">"Key"</span>: <span style="color:#d20;background-color:#fff0f0">"this is the secret key for the jwt, it must be kept secure"</span>,
<span style="color:#b06;font-weight:bold">"Issuer"</span>: <span style="color:#d20;background-color:#fff0f0">"vehiclequotes.endpointdev.com"</span>,
<span style="color:#b06;font-weight:bold">"Audience"</span>: <span style="color:#d20;background-color:#fff0f0">"vehiclequotes.endpointdev.com"</span>,
<span style="color:#b06;font-weight:bold">"Subject"</span>: <span style="color:#d20;background-color:#fff0f0">"JWT for vehiclequotes.endpointdev.com"</span>
}<span style="color:#a61717;background-color:#e3d2d2">,</span>
</code></pre></div><p>These are values that we’ll need when creating the tokens. We’ll go over the purpose of each them as they come up as we continue writing our code.</p>
<blockquote>
<p>Here we’ll gloss over some details on the inner workings of JWTs as a standard. You can learn more about them at <a href="https://jwt.io/introduction">jwt.io</a>.</p>
</blockquote>
<p>For now, let’s add the following class in <code>VehicleQuotes/Services/JwtService.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.IdentityModel.Tokens.Jwt</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Security.Claims</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Text</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.Configuration</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.IdentityModel.Tokens</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Services</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">JwtService</span>
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">const</span> <span style="color:#888;font-weight:bold">int</span> EXPIRATION_MINUTES = <span style="color:#00d;font-weight:bold">1</span>;
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> IConfiguration <span style="color:#00d;font-weight:bold">_</span>configuration;
<span style="color:#080;font-weight:bold">public</span> JwtService(IConfiguration configuration)
{
<span style="color:#00d;font-weight:bold">_</span>configuration = configuration;
}
<span style="color:#080;font-weight:bold">public</span> AuthenticationResponse CreateToken(IdentityUser user)
{
<span style="color:#888;font-weight:bold">var</span> expiration = DateTime.UtcNow.AddMinutes(EXPIRATION_MINUTES);
<span style="color:#888;font-weight:bold">var</span> token = CreateJwtToken(
CreateClaims(user),
CreateSigningCredentials(),
expiration
);
<span style="color:#888;font-weight:bold">var</span> tokenHandler = <span style="color:#080;font-weight:bold">new</span> JwtSecurityTokenHandler();
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> AuthenticationResponse {
Token = tokenHandler.WriteToken(token),
Expiration = expiration
};
}
<span style="color:#080;font-weight:bold">private</span> JwtSecurityToken CreateJwtToken(Claim[] claims, SigningCredentials credentials, DateTime expiration) =>
<span style="color:#080;font-weight:bold">new</span> JwtSecurityToken(
<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Issuer"</span>],
<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Audience"</span>],
claims,
expires: expiration,
signingCredentials: credentials
);
<span style="color:#080;font-weight:bold">private</span> Claim[] CreateClaims(IdentityUser user) =>
<span style="color:#080;font-weight:bold">new</span>[] {
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Sub, <span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Subject"</span>]),
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.NameIdentifier, user.Id),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Name, user.UserName),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Email, user.Email)
};
<span style="color:#080;font-weight:bold">private</span> SigningCredentials CreateSigningCredentials() =>
<span style="color:#080;font-weight:bold">new</span> SigningCredentials(
<span style="color:#080;font-weight:bold">new</span> SymmetricSecurityKey(
Encoding.UTF8.GetBytes(<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Key"</span>])
),
SecurityAlgorithms.HmacSha256
);
}
}
</code></pre></div><p>The <code>CreateToken</code> method is the element that’s most worth discussing here. It receives an instance of <code>IdentityUser</code> as a parameter (which, remember, is the entity class that represents our user accounts), uses it to construct a JWT, and returns it within a <code>AuthenticationResponse</code> object (which is what we decided that our endpoint would return). To do so, we use various classes built into .NET.</p>
<p>The main class that represents the JWT is <code>JwtSecurityToken</code>. We <code>new</code> up one of those in the <code>CreateJwtToken</code> method with this call:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">new</span> JwtSecurityToken (
<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Issuer"</span>],
<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Audience"</span>],
claims,
expires: expiration,
signingCredentials: credentials
);
</code></pre></div><p>“Issuer” and “Audience” are two important values for how JWTs work. They specify which entity is creating the token (i.e. the “Issuer”) and which entity is the token intended for (i.e. the “Audience”). We use the <code>IConfiguration</code> instance that we got as a dependency to fetch their values from the <code>VehicleQuotes/appsettings.json</code> file.</p>
<blockquote>
<p>The “Issuer” and “Audience” parameters in <code>JwtSecurityToken</code>’s constructor correspond to JWT claims <code>iss</code> and <code>aud</code>, respectively. You can learn more about them and other claims in <a href="https://datatracker.ietf.org/doc/html/rfc7519#section-4.1">the RFC</a>.</p>
</blockquote>
<p>The next parameter that <code>JwtSecurityToken</code>’s constructor needs is the claims array. In JWT terms, a claim is essentially a statement about the entity for which the token is generated, some data that identifies it. For example, if we’re generating a token for a user, what you would expect to see in such a token’s claims are things like username, email, and any other non-secret profile info.</p>
<p>In our case, as you can see in the <code>CreateClaims</code> method, we add a number of claims:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">new</span>[] {
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Sub, <span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Subject"</span>]),
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
<span style="color:#080;font-weight:bold">new</span> Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.NameIdentifier, user.Id),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Name, user.UserName),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Email, user.Email)
};
</code></pre></div><p>Along with the user id, name and email, we also add <code>sub</code>, <code>jti</code> and <code>iat</code> claims. These are standardized claims of which you can learn more about in <a href="https://datatracker.ietf.org/doc/html/rfc7519#section-4.1">the RFC</a>. The data that we put in here will make its way into the encoded token that our API caller eventually sees. We’ll see that later.</p>
<p>The next parameter to <code>JwtSecurityToken</code> is the expiration date of the token. Here we are setting it to just one minute in the future to comply with our original requirement that the token should be short-lived so that it only allows a handful of requests over a short period of time.</p>
<p>Finally there’s the <code>signingCredentials</code> parameter which tells the <code>JwtSecurityToken</code> how to cryptographically sign the token. As you can see in the code:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">new</span> SigningCredentials(
<span style="color:#080;font-weight:bold">new</span> SymmetricSecurityKey(
Encoding.UTF8.GetBytes(<span style="color:#00d;font-weight:bold">_</span>configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Key"</span>])
),
SecurityAlgorithms.HmacSha256
);
</code></pre></div><p>That’s a <code>SigningCredentials</code> instance that we create using the “key” that we have configured in <code>VehicleQuotes/appsettings.json</code>, along with the algorithm to use to produce it.</p>
<p>And that’s about it. There isn’t much else to this class. It is somewhat involved but that’s just how JWTs are created in .NET.</p>
<h5 id="the-action-method-that-puts-it-all-together">The action method that puts it all together</h5>
<p>Now all we need to do is actually create an endpoint that exposes this functionality to clients. Since we have the core logic encapsulated in our <code>JwtService</code> class, the actual action method is simple. Here are the changes that we need to make in order to implement it:</p>
<p>First, on <code>VehicleQuotes/Controllers/UsersController.cs</code>, we add a new using statement so that we can reference our new <code>JwtService</code> class:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Services</span>;
</code></pre></div><p>Next, we declare a new parameter of type <code>JwtService</code> in the controller’s constructor so that we signal to ASP.NET Core’s Dependency Injection subsystem that we want to use one of those. We also hold onto it via a new instance variable called <code>_jwtService</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> // ...
public class UsersController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
<span style="color:#000;background-color:#dfd">+ private readonly JwtService _jwtService;
</span><span style="color:#000;background-color:#dfd"></span>
public UsersController(
UserManager<IdentityUser> userManager,
<span style="color:#000;background-color:#dfd">+ JwtService jwtService
</span><span style="color:#000;background-color:#dfd"></span> ) {
_userManager = userManager;
<span style="color:#000;background-color:#dfd">+ _jwtService = jwtService;
</span><span style="color:#000;background-color:#dfd"></span> }
// ...
}
</code></pre></div><p>We also need to tell ASP.NET Core that <code>JwtService</code> should be available for Dependency Injection. To do so, we can add this line in <code>VehicleQuotes/Startup.cs</code>’s <code>ConfigureServices</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services.AddScoped<Services.JwtService>();
</code></pre></div><blockquote>
<p>Note that this way of defining dependencies is not recommended and only done this way here to keep things simple for an illustrative app that’s never going to run in production. Ideally what you want to do here is define the dependency as an abstraction (i.e. an interface) and a concrete implementation that fulfills it. For example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services.AddScoped<ITokenCreationService, JwtService>();
</code></pre></div><p>This way, classes that depend on this service can reference the interface, not the concrete type. In doing that, they adhere to the <a href="https://en.wikipedia.org/wiki/Dependency_inversion_principle">Dependency Inversion</a> principle and become more easily testable because they allow mocks to be provided as dependencies.</p>
</blockquote>
<p>Finally, we write the actual action method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888">// POST: api/Users/BearerToken
</span><span style="color:#888"></span><span style="color:#369">[HttpPost("BearerToken")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<AuthenticationResponse>> CreateBearerToken(AuthenticationRequest request)
{
<span style="color:#080;font-weight:bold">if</span> (!ModelState.IsValid)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(<span style="color:#d20;background-color:#fff0f0">"Bad credentials"</span>);
}
<span style="color:#888;font-weight:bold">var</span> user = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.FindByNameAsync(request.UserName);
<span style="color:#080;font-weight:bold">if</span> (user == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(<span style="color:#d20;background-color:#fff0f0">"Bad credentials"</span>);
}
<span style="color:#888;font-weight:bold">var</span> isPasswordValid = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.CheckPasswordAsync(user, request.Password);
<span style="color:#080;font-weight:bold">if</span> (!isPasswordValid)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(<span style="color:#d20;background-color:#fff0f0">"Bad credentials"</span>);
}
<span style="color:#888;font-weight:bold">var</span> token = <span style="color:#00d;font-weight:bold">_</span>jwtService.CreateToken(user);
<span style="color:#080;font-weight:bold">return</span> Ok(token);
}
</code></pre></div><p>Here too we’re leveraging the <code>UserManager</code> instance that ASP.NET Core Identity so graciously provided us with. In summary, we find the user account by name using the incoming request data, check if the given password is correct, then ask our <code>JwtService</code> to create a token for this user, and finally return it wrapped in a 200 response.</p>
<p>If you hit that endpoint with a POST and a set of existing credentials, you should see something like this:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-bearer-token.webp" alt="Successful JTW creation in Postman"></p>
<p>If you take that big string that came back in the <code>"token"</code> field in the response JSON, and paste it in <a href="https://jwt.io/">jwt.io</a>’s token decoder, you should be able to see all the claims that we added to the token:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/jwt-decoded.webp" alt="The JTW decoded showing all the claims"></p>
<blockquote>
<p>Notice that the keywords here are “Encoded” and “Decoded”. The claims that we put in our JWTs are not protected in any way. As such, we should never put secrets in there.</p>
</blockquote>
<h4 id="securing-an-endpoint-with-jwt-authentication">Securing an endpoint with JWT authentication</h4>
<p>Now that we have a way for obtaining tokens, let’s see how we can actually use them to gain access to some resources. To demonstrate that, let’s secure an endpoint in a way that it denies unauthenticated requests.</p>
<h5 id="applying-the-authorize-attribute">Applying the Authorize attribute</h5>
<p>First we need to signal ASP.NET Core that the endpoint requires auth. We do that by annotating the corresponding action method with the <code>Authorize</code> attribute. We’ve already decided that we were going to use <code>VehicleQuotes/Controllers/BodyTypesController.cs</code>’s <code>GetBodyTypes</code> method as a guinea pig. So, let’s go into that file and add the following <code>using</code> statement:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Authorization</span>;
</code></pre></div><p>That will allow us access to the attribute, which we can apply to the action method like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> // GET: api/BodyTypes
<span style="color:#000;background-color:#dfd">+ [Authorize]
</span><span style="color:#000;background-color:#dfd"></span> [HttpGet]
public async Task<ActionResult<IEnumerable<BodyType>>> GetBodyTypes()
{
return await _context.BodyTypes.ToListAsync();
}
</code></pre></div><p>With this, we’ve told ASP.NET Core that we want it to require auth for this endpoint.</p>
<h5 id="enabling-and-configuring-the-jwt-authentication">Enabling and configuring the JWT authentication</h5>
<p>Now, we need to tell it how to actually perform the check. To do that, we need to install the <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer"><code>Microsoft.AspNetCore.Authentication.JwtBearer</code></a> NuGet package. We can do that from the <code>VehicleQuotes</code> directory with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">$ dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
</code></pre></div><p>Once we have that installed, we get access to additional services which we can use to configure the “JwtBearer” authentication scheme. As usual, we do the configuration in <code>VehicleQuotes/Startup.cs</code>’s. Here’s what we need to do:</p>
<p>Add a few new <code>using</code> statements:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Authentication.JwtBearer</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.IdentityModel.Tokens</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Text</span>;
</code></pre></div><p>Add the following code at the end of the <code>ConfigureServices</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = <span style="color:#080;font-weight:bold">new</span> TokenValidationParameters()
{
ValidateIssuer = <span style="color:#080;font-weight:bold">true</span>,
ValidateAudience = <span style="color:#080;font-weight:bold">true</span>,
ValidateLifetime = <span style="color:#080;font-weight:bold">true</span>,
ValidateIssuerSigningKey = <span style="color:#080;font-weight:bold">true</span>,
ValidAudience = Configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Audience"</span>],
ValidIssuer = Configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Issuer"</span>],
IssuerSigningKey = <span style="color:#080;font-weight:bold">new</span> SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration[<span style="color:#d20;background-color:#fff0f0">"Jwt:Key"</span>])
)
};
});
</code></pre></div><p>The call to <code>AddAuthentication</code> includes all the internal core service classes that are needed to do authentication in our app.</p>
<p>The <code>JwtBearerDefaults.AuthenticationScheme</code> parameter that we give it is the name of the authentication scheme to use as the default. More on that later. For now, know that ASP.NET Core supports multiple authentication schemes to be used at the same time. In fact, our own plan here is to eventually support two auth schemes: JWTs and API Keys. We’re starting with JWT first and as such, that’s the one we specify as the default.</p>
<p>The call to <code>AddJwtBearer</code> configures the JWT authentication scheme. That is, it allows the app to perform authentication checks based on an incoming JWT token.</p>
<p>The most important part of the configuration is the values that we are passing to <code>TokenValidationParameters</code>. As you can see, we are able to specify which aspects of the incoming JWTs to validate. You can see all available options for <code>TokenValidationParameters</code> in <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnet">the official documentation</a>. Here we’ve chosen to validate obvious things like the issuer, audience, and signing key.</p>
<p>The last configuration step is to add this line right before <code>app.UseAuthorization();</code> in the <code>Configure</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">app.UseAuthentication();
</code></pre></div><p>That will enable the authentication <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0">middleware</a>. That way the framework will perform authentication checks as part of the request processing pipeline. In other words, actually put all the configuration that we’ve done to good use.</p>
<p>Alright, now that we have all the configuration ready and have secured an endpoint, let’s try hitting it with a GET on <code>api/BodyTypes</code> and see what happens:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/unauthorized-response.webp" alt="Unauthorized response for unauthenticated request"></p>
<p>Ok! So far so good. We made an unauthenticated request into an endpoint that requires authentication and as a result we got a 401 back. That’s just what we wanted.</p>
<p>Now, let’s get a token by POSTing to <code>api/Users/BearerToken</code>:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-bearer-token-2.webp" alt="Another successful JTW creation in Postman"></p>
<p>We can copy that token and include as a header in the GET request to <code>api/BodyTypes</code>. The header key should be <code>Authorization</code> and the value should be <code>Bearer <our token></code>. In Postman, we can setup a similar request if we choose the “Authorization” tab, select “Bearer Token” in the “Type” drop-down list and paste the token in the “Token” text box.</p>
<p>Do that, and you should now see a 200 response from the endpoint:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/authorized-response-jwt.webp" alt="Successful response for request authenticated with JWT"></p>
<p>Neat! Now that we passed a valid token, it wants to talk to us again. Let a few minutes pass, enough for the token to expire and try the request again to see how it’s 401’ing again.</p>
<h3 id="implementing-api-key-authentication">Implementing API Key authentication</h3>
<p>Now let’s change gears from JWT and implement an alternate authentication strategy in our Web API: API Keys. Authentication with API Keys is fairly common in the web service world.</p>
<p>The core idea of API Keys is that the API provider (in this case, us) produces a secret string that is given to the clients for safekeeping. The clients then can use that key to access the API and make as many requests as they want. So it’s essentially just a glorified password.</p>
<p>The main functional differences when it comes to JWT as we have implemented it is that the API Keys won’t expire and that we will store them in our database. Then, when processing incoming requests, as part of the authentication step, our app will check if the given API Key exists in our internal records. If it does, then the request is allowed to go through, if it does not, then a 401 is sent back.</p>
<p>This is going to be interesting because ASP.NET Core does not include an authentication scheme out of the box for this kind of behavior. For JWT, we were able to use the <code>Microsoft.AspNetCore.Authentication.JwtBearer</code> package. For this we have to get our hands dirty and actually implement a custom authentication handler that will run the logic that we need.</p>
<p>Let’s get started.</p>
<h4 id="creating-api-keys">Creating API Keys</h4>
<p>Similarly to when we implemented JWTs, we’ll start by creating the new endpoint that clients will call to obtain their keys. In order to make it work, we’ll need:</p>
<ol>
<li>A new model and database table to store the keys</li>
<li>A service class to create the keys.</li>
<li>A new action method in <code>UsersController</code> that does the work.</li>
</ol>
<h5 id="the-api-key-model-and-database-table">The API Key model and database table</h5>
<p>For storing API Keys, we just need a simple table that is linked to users via a one-to-many relationship and includes a name and a value. Using Entity Framework, our model could look like this in <code>VehicleQuotes/Models/UserApiKey.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Text.Json.Serialization</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#369"> [Index(nameof(Value), IsUnique = true)]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">UserApiKey</span>
{
<span style="color:#369"> [JsonIgnore]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Value { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [JsonIgnore]</span>
<span style="color:#369"> [Required]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> UserID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#369">
</span><span style="color:#369"> [JsonIgnore]</span>
<span style="color:#080;font-weight:bold">public</span> IdentityUser User { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Easy enough. We express the relationship between the “keys” and “users” tables via the <code>User</code> navigation property and the <code>UserID</code> field. We also define a unique index on the <code>Value</code> field and annotate some fields with <code>Required</code> because we don’t want them to allow null. Interestingly, we also annotated some of the fields with the <code>JsonIgnore</code> attribute so that when we include objects of this type in API responses (which, as you’ll see, we will), those fields are not included.</p>
<p>We also need to add a new <code>DbSet</code> on our DbContext class at <code>VehicleQuotes/Data/VehicleQuotesContext.cs</code> so that Entity Framework picks it up as part of the data model:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">public</span> DbSet<UserApiKey> UserApiKeys { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
</code></pre></div><p>With that, let’s now create and run a migration so that the new table can be added to the database. All it takes is running this to create the migration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">dotnet ef migrations add AddUserApiKeysTable
</code></pre></div><p>And this to apply it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">dotnet ef database update
</code></pre></div><p>Now the new table should appear in the database:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">vehicle_quotes=# \dt
List of relations
Schema | Name | Type | Owner
--------+-----------------------+-------+----------------
...
public | user_api_keys | table | vehicle_quotes
(15 rows)
</code></pre></div><h5 id="the-class-that-creates-api-keys">The class that creates API Keys</h5>
<p>The next step is very straightforward. All we need is some logic that, when given a user account (in the form of an instance of <code>IdentityUser</code>), can generate a new API Key, insert it in the database, and return it. The following unremarkable class, defined in <code>VehicleQuotes/Services/ApiKeyService.cs</code>, encapsulates said logic:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Services</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ApiKeyService</span>
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> VehicleQuotesContext <span style="color:#00d;font-weight:bold">_</span>context;
<span style="color:#080;font-weight:bold">public</span> ApiKeyService(VehicleQuotesContext context)
{
<span style="color:#00d;font-weight:bold">_</span>context = context;
}
<span style="color:#080;font-weight:bold">public</span> UserApiKey CreateApiKey(IdentityUser user)
{
<span style="color:#888;font-weight:bold">var</span> newApiKey = <span style="color:#080;font-weight:bold">new</span> UserApiKey
{
User = user,
Value = GenerateApiKeyValue()
};
<span style="color:#00d;font-weight:bold">_</span>context.UserApiKeys.Add(newApiKey);
<span style="color:#00d;font-weight:bold">_</span>context.SaveChanges();
<span style="color:#080;font-weight:bold">return</span> newApiKey;
}
<span style="color:#080;font-weight:bold">private</span> <span style="color:#888;font-weight:bold">string</span> GenerateApiKeyValue() =>
<span style="color:#d20;background-color:#fff0f0">$"{Guid.NewGuid().ToString()}-{Guid.NewGuid().ToString()}"</span>;
}
}
</code></pre></div><p>As you can see, this class’s single public method, aptly named <code>CreateApiKey</code>, just takes the given user and creates a new <code>UserApiKey</code> that’s associated with it and saves it into the database. The key values themselves are just two <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">GUIDs</a> concatenated.</p>
<blockquote>
<p>This is an admittedly simplistic and not-all-that-secure method for generating the keys themselves. For simplicity, we’ve gone with this. In a production app however, it’d be better to use a true cryptographically secure string. <a href="https://jonathancrozier.com/blog/how-to-generate-a-cryptographically-secure-random-string-in-dot-net-with-c-sharp">Here’s an article</a> that explains how to do that with .NET.</p>
</blockquote>
<p>Naturally, this class needs to be used elsewhere in the application, so let’s make it available for Dependency Injection by adding the following line to <code>VehicleQuotes/Startup.cs</code>’s <code>ConfigureServices</code> method.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp">services.AddScoped<Services.ApiKeyService>();
</code></pre></div><h5 id="the-action-method-that-puts-it-all-together-1">The action method that puts it all together</h5>
<p>Now let’s finally write a new action method on <code>VehicleQuotes/Controllers/UsersController.cs</code> so that there’s an endpoint that clients can call to get API Keys. Before adding the action method though, as usual, we need to declare the following dependency as a constructor parameter and hold it in an instance variable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> public class UsersController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
private readonly JwtService _jwtService;
<span style="color:#000;background-color:#dfd">+ private readonly ApiKeyService _apiKeyService;
</span><span style="color:#000;background-color:#dfd"></span>
public UsersController(
UserManager<IdentityUser> userManager,
JwtService jwtService,
<span style="color:#000;background-color:#dfd">+ ApiKeyService apiKeyService
</span><span style="color:#000;background-color:#dfd"></span> ) {
_userManager = userManager;
_jwtService = jwtService;
<span style="color:#000;background-color:#dfd">+ _apiKeyService = apiKeyService;
</span><span style="color:#000;background-color:#dfd"></span> }}
</code></pre></div><p>The action method itself would look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#888">// POST: api/Users/ApiKey
</span><span style="color:#888"></span><span style="color:#369">[HttpPost("ApiKey")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult> CreateApiKey(AuthenticationRequest request)
{
<span style="color:#080;font-weight:bold">if</span> (!ModelState.IsValid)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(ModelState);
}
<span style="color:#888;font-weight:bold">var</span> user = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.FindByNameAsync(request.UserName);
<span style="color:#080;font-weight:bold">if</span> (user == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(<span style="color:#d20;background-color:#fff0f0">"Bad credentials"</span>);
}
<span style="color:#888;font-weight:bold">var</span> isPasswordValid = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>userManager.CheckPasswordAsync(user, request.Password);
<span style="color:#080;font-weight:bold">if</span> (!isPasswordValid)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest(<span style="color:#d20;background-color:#fff0f0">"Bad credentials"</span>);
}
<span style="color:#888;font-weight:bold">var</span> token = <span style="color:#00d;font-weight:bold">_</span>apiKeyService.CreateApiKey(user);
<span style="color:#080;font-weight:bold">return</span> Ok(token);
}
</code></pre></div><p>This method is very similar to the other one that generates JWTs. Painfully so. In fact the only difference is that this one calls the <code>ApiKeyService</code>’s <code>CreateApiKey</code> method instead of <code>JwtService</code>’s <code>CreateToken</code>, for obvious reasons.</p>
<blockquote>
<p>These two are ripe for some refactoring to eliminate duplication but we won’t do that here. Take a look at the <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/json-web-token">finished project on GitHub</a> to see <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/9a078015cec2e8a42a4898e203a1a50db69731e8">a neat refactoring option</a>.</p>
</blockquote>
<p>Anyway, you can now try a POST into <code>api/Users/ApiKey</code>, pass it a valid set of credentials, and you should see it respond with a brand new API Key:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-api-key.webp" alt="Successful response for authenticated request"></p>
<p>The key can also be found in the database:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">vehicle_quotes=# select id, SUBSTRING(value,1,13), user_id from user_api_keys;
id | substring | user_id
----+---------------+--------------------------------------
1 | efd2133b-cc4a | aaad07b4-f109-4255-8caa-185fd7694c72
(1 row)
</code></pre></div><h4 id="securing-an-endpoint-with-api-key-authentication">Securing an endpoint with API Key authentication</h4>
<p>Now that users have a way of getting API Keys, we can start securing endpoints with that authentication scheme. In order to acomplish that, we need to take three steps:</p>
<ol>
<li>Implement a custom authentication handler that runs the authentication logic.</li>
<li>Configure the new authentication handler, plugging it into ASP.NET Core’s auth mechanisms.</li>
<li>Secure certain endpoints via the Authorization attribute, specifying the scheme that we want to use to do so.</li>
</ol>
<p>We’ll do that next.</p>
<h5 id="implementing-the-api-key-authentication-handler">Implementing the API Key authentication handler</h5>
<p>Like we’ve touched on before, the “authentication handler” is a class that can plug into ASP.NET Core’s authentication middleware and can run the authentication logic for a given authentication scheme. In practical terms, it’s a class that inherits from <code>Microsoft.AspNetCore.Authentication.AuthenticationHandler</code> and implements the <code>HandleAuthenticateAsync</code> method. Ours will live in <code>VehicleQuotes/Authentication/ApiKey/ApiKeyAuthenticationHandler.cs</code> and it looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Security.Claims</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Text.Encodings.Web</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Threading.Tasks</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Authentication</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Identity</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.Logging</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.Options</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Authentication.ApiKey</span>
{
<span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ApiKeyAuthenticationHandler</span> : AuthenticationHandler<AuthenticationSchemeOptions>
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">const</span> <span style="color:#888;font-weight:bold">string</span> API_KEY_HEADER = <span style="color:#d20;background-color:#fff0f0">"Api-Key"</span>;
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> VehicleQuotesContext <span style="color:#00d;font-weight:bold">_</span>context;
<span style="color:#080;font-weight:bold">public</span> ApiKeyAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
VehicleQuotesContext context
) : <span style="color:#080;font-weight:bold">base</span>(options, logger, encoder, clock)
{
<span style="color:#00d;font-weight:bold">_</span>context = context;
}
<span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">async</span> Task<AuthenticateResult> HandleAuthenticateAsync()
{
<span style="color:#080;font-weight:bold">if</span> (!Request.Headers.ContainsKey(API_KEY_HEADER))
{
<span style="color:#080;font-weight:bold">return</span> AuthenticateResult.Fail(<span style="color:#d20;background-color:#fff0f0">"Header Not Found."</span>);
}
<span style="color:#888;font-weight:bold">string</span> apiKeyToValidate = Request.Headers[API_KEY_HEADER];
<span style="color:#888;font-weight:bold">var</span> apiKey = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.UserApiKeys
.Include(uak => uak.User)
.SingleOrDefaultAsync(uak => uak.Value == apiKeyToValidate);
<span style="color:#080;font-weight:bold">if</span> (apiKey == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> AuthenticateResult.Fail(<span style="color:#d20;background-color:#fff0f0">"Invalid key."</span>);
}
<span style="color:#080;font-weight:bold">return</span> AuthenticateResult.Success(CreateTicket(apiKey.User));
}
<span style="color:#080;font-weight:bold">private</span> AuthenticationTicket CreateTicket(IdentityUser user)
{
<span style="color:#888;font-weight:bold">var</span> claims = <span style="color:#080;font-weight:bold">new</span>[] {
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.NameIdentifier, user.Id),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Name, user.UserName),
<span style="color:#080;font-weight:bold">new</span> Claim(ClaimTypes.Email, user.Email)
};
<span style="color:#888;font-weight:bold">var</span> identity = <span style="color:#080;font-weight:bold">new</span> ClaimsIdentity(claims, Scheme.Name);
<span style="color:#888;font-weight:bold">var</span> principal = <span style="color:#080;font-weight:bold">new</span> ClaimsPrincipal(identity);
<span style="color:#888;font-weight:bold">var</span> ticket = <span style="color:#080;font-weight:bold">new</span> AuthenticationTicket(principal, Scheme.Name);
<span style="color:#080;font-weight:bold">return</span> ticket;
}
}
}
</code></pre></div><p>The first thing to note is how we’ve defined the base class: <code>AuthenticationHandler<AuthenticationSchemeOptions></code>. <code>AuthenticationHandler</code> is a generic, and it allows us to control the type of options object that it accepts. In this case, we’ve used <code>AuthenticationSchemeOptions</code>, which is just a default one already provided by the framework. This is because we don’t really need any custom options.</p>
<blockquote>
<p>If we did, then we would define a new class that inherits from <code>AuthenticationSchemeOptions</code>, give it the new fields that we need, and use that as a generic time parameter instead. We would then be able to set values for these new fields during configuration in the app’s <code>Startup</code> class. Just like we’ve done with the “Jwt Bearer” auth scheme via the <code>AddJwtBearer</code> method.</p>
</blockquote>
<p>The next point of interest in this class is its constructor. The key aspect of it is that it calls base and passes it a series of parameters that it itself gets. This is because <code>AuthenticationHandler</code> does have some constructor logic that needs to be run for things to work properly, so we make sure to do that.</p>
<p>Finally, there’s the <code>HandleAuthenticateAsync</code> method which does the actual authentication. This method inspects the incoming request looking for an “Api-Key” header. If it finds it, it takes its value and makes a query into the database to try and find a record in the <code>user_api_keys</code> table that matches the incoming value. If it finds that, it creates an <code>AuthenticationTicket</code> which contains the identity of the user that the key belongs to, and returns it wrapped in a <code>AuthenticateResult.Success</code>. That return value signals ASP.NET Core’s authentication middleware that the request is authentic. Otherwise, it returns <code>AuthenticateResult.Fail</code>, which prompts ASP.NET Core to halt the request and return a 401.</p>
<p>Something interesting to note is how the <code>AuthenticationTicket</code>, similarly to the JWT Bearer scheme’s <code>JwtSecurityToken</code>, also includes an array of “claims” that serve to identify the user that has been authenticated. The “claims” concept is central to how auth works in ASP.NET Core.</p>
<h5 id="enabling-and-configuring-the-api-key-authentication">Enabling and configuring the API Key authentication</h5>
<p>Now we need to tell the framework that we want it to use a new authentication scheme for our app, spearheaded by the custom authentication handler that we just wrote. There are two steps to make that happen.</p>
<p>First, we configure our new scheme in the app’s <code>Startup</code> class. For that, we need these two new <code>using</code> statements:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Authentication</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Authentication.ApiKey</span>;
</code></pre></div><p>Then we add this right after the <code>AddJwtBearer</code> call in the <code>ConfigureServices</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> public void ConfigureServices(IServiceCollection services)
{
// ...
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = Configuration["Jwt:Audience"],
ValidIssuer = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])
)
};
})
<span style="color:#000;background-color:#dfd">+ .AddScheme<AuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(
</span><span style="color:#000;background-color:#dfd">+ "ApiKey",
</span><span style="color:#000;background-color:#dfd">+ options => { }
</span><span style="color:#000;background-color:#dfd">+ );
</span><span style="color:#000;background-color:#dfd"></span> }
</code></pre></div><p>A simple configuration as far as auth schemes go. We specify both the types of the authentication handler and the options object that it accepts via the generic type parameters to the <code>AddScheme</code> method. <code>"ApiKey"</code> is just the name we’ve given our custom auth scheme. It can be anything as long as it’s not “Bearer”, which is the name of the Jwt Bearer scheme (which is the value returned by <code>JwtBearerDefaults.AuthenticationScheme</code>). We’ll refer back to it later. Finally, since we have no special options to give it, we specify a lambda that does nothing with its options.</p>
<h5 id="updating-the-authorize-attribute-to-use-our-two-schemes">Updating the Authorize attribute to use our two schemes</h5>
<p>Now that the auth scheme is configured, we need to use it to secure an endpoint. We can use the same that we used for JWT: <code>POST api/BodyTypes</code>, defined in <code>BodyTypesController</code>’s <code>GetBodyTypes</code> action method. Remember though, that we wanted to have both auth schemes (JWT and API Key) work on the endpoint. For that, the <code>Authorize</code> attribute allows a comma separated string of scheme names as a parameter. So, we can get both our configured schemes working if we update the attribute like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#369">[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme},ApiKey")]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<IEnumerable<BodyType>>> GetBodyTypes()
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.BodyTypes.ToListAsync();
}
</code></pre></div><p><code>JwtBearerDefaults.AuthenticationScheme</code> contains the name of the JWT Bearer auth scheme. Next to it, we just put “ApiKey”, which is the name we have given our new custom one.</p>
<blockquote>
<p>It’d be nice to put that “ApiKey” string somewhere safe similar to how the “Jwt Bearer” scheme has it in a constant. Take a look at the <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/json-web-token">finished project on GitHub</a> to see <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/8e9432068bfad977980c7c28702773a25ee293b8">one way to organize that</a> and also how to <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/05138e927986fc191d4a042a3455ed1ee1947b8f">make the <code>Startup</code> configuration a little less verbose</a> by using extension methods.</p>
</blockquote>
<p>And finally, let’s test our endpoint and marvel at the fruit of our work.</p>
<p>Make sure to create a new API Key by POSTing to <code>api/Users/ApiKey</code>, and copy the “value” from the response. We can use it as the value for the <code>Api-Key</code> header a GET request to <code>api/BodyTypes</code>. In Postman, we can do that by choosing the “Authorization” tab, selecting “API Key” in the “Type” drop-down list, writing “Api-Key” in the “Key” text box, and putting our key in the “Value” text box.</p>
<p>With that, we can make the request and see how the endpoint now allows authentication via API Keys:</p>
<p><img src="/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/authorized-response-api-key.webp" alt="Successful response for request authenticated with API Key"></p>
<p>Of course, requests authenticated via JWT will also work for this endpoint.</p>
<h3 id="thats-all">That’s all</h3>
<p>And there you have it! We’ve updated an existing ASP.NET Core Web API application so that it supports authentication using two strategies: JWT and API Keys. We leveraged the Identity libraries to securely store and manage user accounts. We used ASP.NET Core’s built-in authentication capabilities to enable JWT generation and usage. For API Keys, the framework didn’t provide an implementation out of the box. However, it proved to be extensible enough so that we could implement our own.</p>
<h3 id="table-of-contents">Table of contents</h3>
<ul>
<li><a href="#two-approaches-to-authentication-jwt-and-api-keys">Two approaches to authentication: JWT and API Keys</a></li>
<li><a href="#managing-user-accounts-with-aspnet-core-identity">Managing user accounts with ASP.NET Core Identity</a>
<ul>
<li><a href="#install-the-necessary-nuget-packages">Install the necessary NuGet packages</a></li>
<li><a href="#update-the-dbcontext-to-include-the-identity-tables">Update the DbContext to include the Identity tables</a></li>
<li><a href="#applying-database-changes">Applying database changes</a></li>
<li><a href="#configuring-the-identity-services">Configuring the Identity services</a></li>
<li><a href="#creating-users">Creating users</a></li>
<li><a href="#fetching-users">Fetching users</a></li>
</ul>
</li>
<li><a href="#implementing-jwt-bearer-token-authentication">Implementing JWT Bearer Token authentication</a>
<ul>
<li><a href="#creating-tokens">Creating tokens</a>
<ul>
<li><a href="#the-request-data-structure">The request data structure</a></li>
<li><a href="#the-response-data-structure">The response data structure</a></li>
<li><a href="#the-class-that-creates-jwts">The class that creates JWTs</a></li>
<li><a href="#the-action-method-that-puts-it-all-together">The action method that puts it all together</a></li>
</ul>
</li>
<li><a href="#securing-an-endpoint-with-jwt-authentication">Securing an endpoint with JWT authentication</a>
<ul>
<li><a href="#applying-the-authorize-attribute">Applying the Authorize attribute</a></li>
<li><a href="#enabling-and-configuring-the-jwt-authentication">Enabling and configuring the JWT authentication</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#implementing-api-key-authentication">Implementing API Key authentication</a>
<ul>
<li><a href="#creating-api-keys">Creating API Keys</a>
<ul>
<li><a href="#the-api-key-model-and-database-table">The API Key model and database table</a></li>
<li><a href="#the-class-that-creates-api-keys">The class that creates API Keys</a></li>
<li><a href="#the-action-method-that-puts-it-all-together-1">The action method that puts it all together</a></li>
</ul>
</li>
<li><a href="#securing-an-endpoint-with-api-key-authentication">Securing an endpoint with API Key authentication</a>
<ul>
<li><a href="#implementing-the-api-key-authentication-handler">Implementing the API Key authentication handler</a></li>
<li><a href="#enabling-and-configuring-the-api-key-authentication">Enabling and configuring the API Key authentication</a></li>
<li><a href="#updating-the-authorize-attribute-to-use-our-two-schemes">Updating the Authorize attribute to use our two schemes</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#thats-all">That’s all</a></li>
</ul>
Monitoring Settings Changes in ASP.NET Corehttps://www.endpointdev.com/blog/2021/09/monitoring-settings-changes-in-asp-net-core/2021-09-22T00:00:00+00:00Daniel Gomm
<p><img src="/blog/2021/09/monitoring-settings-changes-in-asp-net-core/ripples.jpg" alt="Ripples in water"><br>
<a href="https://unsplash.com/photos/Q5QspluNZmM">Photo</a> by <a href="https://unsplash.com/@dreamsoftheoceans">Linus Nylund</a> on Unsplash</p>
<p>Did you know you can directly respond to config file changes in ASP.NET Core? By using the <code>IOptionsMonitor<T></code> interface, it’s possible to run a lambda function every time a config file is updated.</p>
<p>For those who aren’t too familiar with it, ASP.NET Core configuration is handled using the <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0">Options Pattern</a>. That is, you create a model class that has a field for each property in your config file, and then set up the application to map the configuration to that class, which can be injected into controllers and services. In your controllers, you can request the configuration class <code>T</code> as a dependency by using an options wrapper class like <code>IOptions<T></code>.</p>
<p>While this works just fine in some cases, it doesn’t allow you to actually run code when the configuration is changed. However, if you request your configuration using <code>IOptionsMonitor<T></code>, you get the ability to define a lambda function to run every time <code>appsettings.json</code> is changed.</p>
<p>One use case for this functionality would be if you wanted to maintain a list in the config file, and log every time that list was changed. In this article I’ll explain how to set up an ASP.NET Core 5.0 API to run custom code whenever changes are made to <code>appsettings.json</code>.</p>
<h3 id="setting-up-the-api">Setting up the API</h3>
<p>In order to use the options pattern in your API, you’ll first need to add the options services to the container using the <code>services.AddOptions()</code> method. Then, you can register your custom configuration class (in this example, <code>MyOptions</code>) to be bound to a specific section in <code>appsettings.json</code> (in this example, <code>"myOptions"</code>).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">void</span> ConfigureServices(IServiceCollection services)
{
<span style="color:#888">// ...
</span><span style="color:#888"></span>
<span style="color:#888">// Add options services to the container
</span><span style="color:#888"></span> services.AddOptions();
<span style="color:#888">// Configure the app to map the "myOptions" section of
</span><span style="color:#888"></span> <span style="color:#888">// the config file to the MyOptions model class
</span><span style="color:#888"></span> services.Configure<MyOptions>(
Configuration.GetSection(<span style="color:#d20;background-color:#fff0f0">"myOptions"</span>)
);
}
</code></pre></div><h3 id="monitoring-configuration-changes">Monitoring Configuration Changes</h3>
<p>Now that the API is set up correctly, in your controllers you can directly request the configuration using <code>IOptionsMonitor<T></code>. You can also unpack the configuration instance itself by using the <code>IOptionsMonitor<T>.CurrentValue</code> property. This value will automatically get updated by <code>IOptionsMonitor<T></code> when the configuration is changed, so you only need to retrieve it once in the constructor.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Threading.Tasks</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Mvc</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.Extensions.Options</span>;
<span style="color:#369">
</span><span style="color:#369">[Route("api/[controller]</span><span style="color:#d20;background-color:#fff0f0">")]
</span><span style="color:#d20;background-color:#fff0f0"></span><span style="color:#369">[ApiController]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">MyController</span> : ControllerBase
{
<span style="color:#080;font-weight:bold">private</span> MyOptions MyOptions;
<span style="color:#080;font-weight:bold">public</span> MyController(IOptionsMonitor<MyOptions> MyOptionsMonitor)
{
<span style="color:#888">// Stores the actual configuration in the controller
</span><span style="color:#888"></span> <span style="color:#888">// to reference later. Changes to the config will be
</span><span style="color:#888"></span> <span style="color:#888">// reflected in MyOptions.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">this</span>.MyOptions = MyOptionsMonitor.CurrentValue;
<span style="color:#888">// Registers a lambda function to be called every time
</span><span style="color:#888"></span> <span style="color:#888">// a configuration change is made
</span><span style="color:#888"></span> MyOptionsMonitor.OnChange(<span style="color:#080;font-weight:bold">async</span> (opts) =>
{
<span style="color:#888">// Write some code!
</span><span style="color:#888"></span> });
}
}
</code></pre></div><p>One small gotcha worth noting is that the OnChange function isn’t debounced. So if you’re using an IDE that automatically saves as you type, it’ll trigger the <code>OnChange</code> function rapidly as you edit <code>appsettings.json</code>.</p>
<h3 id="conclusion">Conclusion</h3>
<p>And that’s it! It doesn’t take much to get your API to run custom code on config file changes.</p>
<p>Have any questions? Feel free to leave a comment!</p>
Building REST APIs with .NET 5, ASP.NET Core, and PostgreSQLhttps://www.endpointdev.com/blog/2021/07/dotnet-5-web-api/2021-07-09T00:00:00+00:00Kevin Campusano
<p><img src="/blog/2021/07/dotnet-5-web-api/market-cropped.jpg" alt="A market at night">
<a href="https://unsplash.com/photos/cpbWNtkKoiU">Photo</a> by <a href="https://unsplash.com/@sam_beasley">Sam Beasley</a></p>
<p>This is old news by now, but I’m still amazed by the fact that nowadays <a href="https://dotnet.microsoft.com/platform/open-source">.NET is open source and can run on Linux</a>. I truly believe that this new direction can help the technology realize its true potential, since it’s no longer shackled to Windows-based environments. I’ve personally been outside the .NET game for a good while, but with <a href="https://docs.microsoft.com/en-us/dotnet/core/dotnet-five">the milestone release that is .NET 5</a>, I think now is a great time to dive back in.</p>
<p>So I thought of taking some time to do just that, really dive in, see what’s new, and get a sense of the general developer experience that the current incarnation of .NET offers. So in this blog post, I’m going to chronicle my experience developing a simple but complete <a href="https://www.redhat.com/en/topics/api/what-is-a-rest-api">REST API</a> application. Along the way, I’ll touch on the most common problems that one runs into when developing such applications and how they are solved in the .NET world. So think of this piece as a sort of tutorial or overview of the most common framework features when it comes to developing REST APIs.</p>
<blockquote>
<p>There’s a <a href="#table-of-contents">table of contents</a> at the bottom.</p>
</blockquote>
<p>First, let’s get familiar with what we’re building.</p>
<h3 id="what-were-building">What we’re building</h3>
<h4 id="the-demo-application">The demo application</h4>
<blockquote>
<p>You can find the finished product on my <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api">GitHub</a>.</p>
</blockquote>
<p>The application that we’ll be building throughout this article will address a request from a hypothetical car junker business. Our client wants to automate the process of calculating how much money to offer their customers for their vehicles, given certain information about them. And they want an app to do that. We are building the back-end component that will support that app. It is a REST API that allows users to provide vehicle information (year, make, model, condition, etc.) and will produce a quote of how much money our hypothetical client would be willing to pay for it.</p>
<p>Here’s a short list of features that we need to implement in order to fulfill that requirement:</p>
<ol>
<li>Given a vehicle model and condition, calculate a price.</li>
<li>Store and manage rules that are used to calculate vehicle prices.</li>
<li>Store and manage pricing overrides on a vehicle model basis. Price overrides are used regardless of the current rules.</li>
<li>CRUD vehicle models so that overrides can be specified for them.</li>
</ol>
<h4 id="the-data-model">The data model</h4>
<p>Here’s what our data model looks like:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/data-model.png" alt="Data Model"></p>
<p>The main table in our model is the <code>quotes</code> table. It stores all the requests for quotes received from our client’s customers. It captures all the relevant vehicle information in terms of model and condition. It also captures the offered quote; that is, the money value that our system calculates for their vehicle.</p>
<p>The <code>quotes</code> table includes all the fields that identify a vehicle: year, make, model, body style and size. It also includes a <code>model_style_year_id</code> field which is an optional foreign key to another table. This FK points to the <code>model_style_years</code> table which contains specific vehicle models that our system can store explicitly.</p>
<p>The idea of this is that, when a customer submits a request for a quote, if we have their vehicle registered in our database, then we can populate this foreign key and link the quote with the specific vehicle that it’s quoting. If we don’t have their vehicle registered, then we leave that field unpopulated. Either way, we can offer a quote. The only difference is the level or certainty of the quote.</p>
<p>The records in the <code>model_style_years</code> table represent specific vehicles. That whole hierarchy works like this: A vehicle make (e.g. Honda, Toyota, etc. in the <code>makes</code> table) has many models (e.g. Civic, Corolla, etc. in the <code>models</code> table), each model has many styles (the <code>model_styles</code> table). Styles are combinations of body types (the <code>body_types</code> table) and sizes (e.g. Mid-size Sedan, Compact Coupe, etc. in the <code>sizes</code> table). And finally, each model style has many years in which they were being produced (via the <code>model_style_years</code> table).</p>
<p>This model allows us very fine-grained differentiation between vehicles. For example, we can have a “2008 Honda Civic Hatchback which is a Compact car” and also a “1990 Honda Civic Hatchback which is a Sub-compact”. That is, same model, different year, size or body type.</p>
<p>We also have a <code>quote_rules</code> table which stores the rules that are applied when it comes to calculating a vehicle quote. The rules are pairs of key-values with an associated monetary value. So for example, rules like “a vehicle that has alloy wheels is worth $10 more” can be expressed in the table with a record where <code>feature_type</code> is “has_alloy_wheels”, <code>feature_value</code> is “true” and <code>price_modifier</code> is “10”.</p>
<p>Finally, we have a <code>quote_overrides</code> table which specifies a flat, static price for specific vehicles (via the link to the <code>model_style_years</code> table). The idea here is that if some customer requests a quote for a vehicle for which we have an override, no price calculation rules are applied and they are offered what is specified in the override record.</p>
<h3 id="the-development-environment">The development environment</h3>
<h4 id="setting-up-the-postgresql-database-with-docker">Setting up the PostgreSQL database with Docker</h4>
<p>For this project, our database of choice is <a href="https://www.postgresql.org/">PostgreSQL</a>. Luckily for us, getting a PostgreSQL instance up and running is very easy thanks to <a href="https://www.docker.com/">Docker</a>.</p>
<blockquote>
<p>If you want to learn more about dockerizing a typical web application, take a look at <a href="/blog/2020/08/containerizing-magento-with-docker-compose-elasticsearch-mysql-and-magento/">this article</a> that explains the process in detail.</p>
</blockquote>
<p>Once you have <a href="https://docs.docker.com/get-docker/">Docker installed</a> in your machine, getting a PostgreSQL instance is as simple as running the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ docker run -d <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> --name vehicle-quote-postgres <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -p 5432:5432 <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> --network host <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -e <span style="color:#369">POSTGRES_DB</span>=vehicle_quote <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -e <span style="color:#369">POSTGRES_USER</span>=vehicle_quote <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -e <span style="color:#369">POSTGRES_PASSWORD</span>=password <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> postgres
</code></pre></div><p>Here we’re asking Docker to run a new <a href="https://docs.docker.com/get-started/#what-is-a-container">container</a> based on the latest <code>postgres</code> <a href="https://docs.docker.com/get-started/#what-is-a-container-image">image</a> from <a href="https://hub.docker.com/_/postgres">DockerHub</a>, name it <code>vehicle-quote-postgres</code>, specify the port to use the default PostgreSQL one, make it accessible to the local network (with the <code>--network host</code> option) and finally, specify a few environment variables that the <code>postgres</code> image uses when building our new instance to set up the default database name, user and password (with the three <code>-e</code> options).</p>
<p>After Docker is done working its magic, you should be able to access the database with something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">$ docker exec -it vehicle-quote-postgres psql -U vehicle_quote
psql (13.2 (Debian 13.2-1.pgdg100+1))
Type "help" for help.
vehicle_quote=#
</code></pre></div><p>This command is connecting to our new <code>vehicle-quote-postgres</code> container and then, from within the container, using the <a href="https://www.postgresql.org/docs/current/app-psql.html">command line client psql</a> in order to connect to the database.</p>
<p>If you have <a href="https://www.compose.com/articles/postgresql-tips-installing-the-postgresql-client/">psql installed</a> on your own machine, you can use it directly to connect to the PostgreSQL instance running inside the container:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ psql -h localhost -U vehicle_quote
</code></pre></div><p>This is possible because we specified in our <code>docker run</code> command that the container would be accepting traffic over port 5432 (<code>-p 5432:5432</code>) and that it would be accesible within the same network as our actual machine (<code>--network host</code>).</p>
<h4 id="installing-the-net-5-sdk">Installing the .NET 5 SDK</h4>
<p>Ok, with that out of the way, let’s install .NET 5.</p>
<p>.NET 5 truly is multi-platform, so whatever environment you prefer to work with, they’ve got you covered. You can go to <a href="https://dotnet.microsoft.com/download/dotnet/5.0">the .NET 5 download page</a> and pick your desired flavor of the SDK.</p>
<p>On Ubuntu 20.10, which is what I’m running, installation is painless. It’s your typical process with <a href="https://en.wikipedia.org/wiki/APT_(software)">APT</a> and <a href="https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#2010-">this page from the official docs</a> has all the details.</p>
<p>First step is to add the Microsoft package repository:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ wget https://packages.microsoft.com/config/ubuntu/20.10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb
</code></pre></div><p>Then, install .NET 5 with APT like one would any other software package:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ sudo apt-get update; <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> sudo apt-get install -y apt-transport-https && <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> sudo apt-get update && <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> sudo apt-get install -y dotnet-sdk-5.0
</code></pre></div><p>Run <code>dotnet --version</code> in your console and you should see something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet --version
5.0.301
</code></pre></div><h3 id="setting-up-the-project">Setting up the project</h3>
<h4 id="creating-our-aspnet-core-rest-api-project">Creating our ASP.NET Core REST API project</h4>
<p>Ok now that we have our requirements, database and SDK, let’s start setting up our project. We do so with the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet new webapi -o VehicleQuotes
</code></pre></div><p>This instructs the <code>dotnet</code> command line tool to create a new REST API web application project for us in a new <code>VehicleQuotes</code> directory.</p>
<p>As a result, <code>dotnet</code> will give you some messages, including this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">The template "ASP.NET Core Web API" was created successfully.
</code></pre></div><p>A new directory was created with our web application files. The newly created <code>VehicleQuotes</code> project looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">.
├── appsettings.Development.json
├── appsettings.json
├── Controllers
│ └── WeatherForecastController.cs
├── obj
│ ├── project.assets.json
│ ├── project.nuget.cache
│ ├── VehicleQuotes.csproj.nuget.dgspec.json
│ ├── VehicleQuotes.csproj.nuget.g.props
│ └── VehicleQuotes.csproj.nuget.g.targets
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── VehicleQuotes.csproj
└── WeatherForecast.cs
</code></pre></div><p>Important things to note here are the <code>appsettings.json</code> and <code>appsettings.Development.json</code> files which contain environment specific configuration values; the <code>Controllers</code> directory where we define our application controllers and action methods (i.e. our REST API endpoints); the <code>Program.cs</code> and <code>Startup.cs</code> files that contain our application’s entry point and bootstrapping logic; and finally <code>VehicleQuotes.csproj</code> which is the file that contains project-wide configuration that the framework cares about like references, compilation targets, and other options. Feel free to explore.</p>
<p>The <code>dotnet new</code> command has given us quite a bit. These files make up a fully working application that we can run and play around with. It even has a <a href="https://swagger.io/tools/swagger-ui/">Swagger UI</a>, as I’ll demonstrate shortly. It’s a great place to get started from.</p>
<blockquote>
<p>You can also get a pretty comprehensive <code>.gitignore</code> file by running the <code>dotnet new gitignore</code> command.</p>
</blockquote>
<p>From inside the <code>VehicleQuotes</code> directory, you can run the application with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet run
</code></pre></div><p>Which will start up a development server and give out the following output:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet run
Building...
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /home/kevin/projects/endpoint/blog/VehicleQuotes
</code></pre></div><p>Open up a browser window and go to <code>https://localhost:5001/swagger</code> to find a Swagger UI listing our API’s endpoints:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/initial-swagger.png" alt="Initial Swagger UI"></p>
<p>As you can see we’ve got a <code>GET /WeatherForecast</code> endpoint in our app. This is included by default in the <code>webapi</code> project template that we specified in our call to <code>dotnet new</code>. You can see it defined in the <code>Controllers/WeatherForecastController.cs</code> file.</p>
<h4 id="installing-packages-well-need">Installing packages we’ll need</h4>
<p>Now let’s install all the tools and libraries we will need for our application. First, we install the <a href="https://www.nuget.org/packages/dotnet-aspnet-codegenerator/">ASP.NET Code Generator</a> tool which we’ll use later for scaffolding controllers:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet tool install --global dotnet-aspnet-codegenerator
</code></pre></div><p>We also need to install the <a href="https://www.nuget.org/packages/dotnet-ef/">Entity Framework command line tools</a> which help us with creating and applying database migrations:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet tool install --global dotnet-ef
</code></pre></div><p>Now, we need to install a few libraries that we’ll use in our project. First are all the packages that allow us to use <a href="https://docs.microsoft.com/en-us/ef/">Entity Framework Core</a>, provide scaffolding support and give us a detailed debugging page for database errors:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
$ dotnet add package Microsoft.EntityFrameworkCore.Design
$ dotnet add package Microsoft.EntityFrameworkCore.SqlServer
$ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
</code></pre></div><p>We also need the <a href="https://www.npgsql.org/efcore/">EF Core driver for PostgreSQL</a> which will allow us to interact with our database:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
</code></pre></div><p>Finally, we need <a href="https://github.com/efcore/EFCore.NamingConventions">another package</a> that will allow us to use the <a href="https://en.wikipedia.org/wiki/Snake_case">snake case</a> naming convention for our database tables, fields, etc. We need this because EF Core uses <a href="https://wiki.c2.com/?UpperCamelCase">capitalized camel case</a> by default, which is not very common in the PostgreSQL world, so this will allow us to play nice. This is the package:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet add package EFCore.NamingConventions
</code></pre></div><h4 id="connecting-to-the-database-and-performing-initial-app-configuration">Connecting to the database and performing initial app configuration</h4>
<p>In order to connect to, query, and modify a database using EF Core, we need to create a <a href="https://docs.microsoft.com/en-us/ef/core/dbcontext-configuration/"><code>DbContext</code></a>. This is a class that serves as the entry point into the database. Create a new directory called <code>Data</code> in the project root and add this new <code>VehicleQuotesContext.cs</code> file to it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">VehicleQuotesContext</span> : DbContext
{
<span style="color:#080;font-weight:bold">public</span> VehicleQuotesContext (DbContextOptions<VehicleQuotesContext> options)
: <span style="color:#080;font-weight:bold">base</span>(options)
{
}
}
}
</code></pre></div><p>As you can see this is just a simple class that inherits from EF Core’s <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext?view=efcore-5.0"><code>DbContext</code></a> class. That’s all we need for now. We will continue building on this class as we add new tables and configurations.</p>
<p>Now, we need to add this class into <a href="https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-5.0">ASP.NET Core’s</a> built-in <a href="https://martinfowler.com/articles/injection.html">IoC (inversion of control) container</a> so that it’s available to controllers and other classes via <a href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a>, and tell it how to find our database. Go to <code>Startup.cs</code> and add the following using statement near the top of the file:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
</code></pre></div><p>That will allow us to do the following change in the <code>ConfigureServices</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> public void ConfigureServices(IServiceCollection services)
{
// ...
<span style="color:#000;background-color:#dfd">+ services.AddDbContext<VehicleQuotesContext>(options =>
</span><span style="color:#000;background-color:#dfd">+ options
</span><span style="color:#000;background-color:#dfd">+ .UseNpgsql(Configuration.GetConnectionString("VehicleQuotesContext"))
</span><span style="color:#000;background-color:#dfd">+ );
</span><span style="color:#000;background-color:#dfd"></span> }
</code></pre></div><blockquote>
<p><code>UseNpgsql</code> is an <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">extension method</a> made available to us by the <code>Npgsql.EntityFrameworkCore.PostgreSQL</code> package that we installed in the previous step.</p>
</blockquote>
<p>The <code>services</code> variable contains all the objects (known as “services”) that are available in the app for Dependency Injection. So here, we’re adding our newly created <code>DbContext</code> to it, specifying that it will connect to a PostgreSQL database (via the <code>options.UseNpgsql</code> call), and that it will use a connection string named <code>VehicleQuotesContext</code> from the app’s default configuration file. So let’s add the connection string then. To do so, change the <code>appsettings.json</code> like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"> {
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
<span style="color:#000;background-color:#fdd">- "AllowedHosts": "*"
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ "AllowedHosts": "*",
</span><span style="color:#000;background-color:#dfd">+ "ConnectionStrings": {
</span><span style="color:#000;background-color:#dfd">+ "VehicleQuotesContext": "Host=localhost;Database=vehicle_quote;Username=vehicle_quote;Password=password"
</span><span style="color:#000;background-color:#dfd">+ }
</span><span style="color:#000;background-color:#dfd"></span> }
</code></pre></div><p>This is your typical PostgreSQL connection string. The only gotcha is that it needs to be specified under the <code>ConnectionStrings</code> -> <code>VehicleQuotesContext</code> section so that our call to <code>Configuration.GetConnectionString</code> can find it.</p>
<p>Now let’s put the <code>EFCore.NamingConventions</code> package to good use and configure EF Core to use snake case when naming database objects. Add the following to the <code>ConfigureServices</code> method in <code>Startup.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<VehicleQuotesContext>(options =>
options
.UseNpgsql(Configuration.GetConnectionString("VehicleQuotesContext"))
<span style="color:#000;background-color:#dfd">+ .UseSnakeCaseNamingConvention()
</span><span style="color:#000;background-color:#dfd"></span> );
}
</code></pre></div><blockquote>
<p><code>UseSnakeCaseNamingConvention</code> is an extension method made available to us by the <code>EFCore.NamingConventions</code> package that we installed in the previous step.</p>
</blockquote>
<p>Now let’s make logging a little bit more verbose with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<VehicleQuotesContext>(options =>
options
.UseNpgsql(Configuration.GetConnectionString("VehicleQuotesContext"))
.UseSnakeCaseNamingConvention()
<span style="color:#000;background-color:#dfd">+ .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()))
</span><span style="color:#000;background-color:#dfd">+ .EnableSensitiveDataLogging()
</span><span style="color:#000;background-color:#dfd"></span> );
}
</code></pre></div><p>This will make sure full database queries appear in the log in the console, including parameter values. This could expose sensitive data so be careful when using <code>EnableSensitiveDataLogging</code> in production.</p>
<p>We can also add the following service configuration to have the app display detailed error pages when something related to the database or migrations goes wrong:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">public void ConfigureServices(IServiceCollection services)
{
// ...
<span style="color:#000;background-color:#dfd">+ services.AddDatabaseDeveloperPageExceptionFilter();
</span><span style="color:#000;background-color:#dfd"></span>}
</code></pre></div><blockquote>
<p><code>AddDatabaseDeveloperPageExceptionFilter</code> is an extension method made available to us by the <code>Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore</code> package that we installed in the previous step.</p>
</blockquote>
<p>Finally, one last configuration I like to do is have the Swagger UI show up at the root URL, so that instead of using <code>https://localhost:5001/swagger</code>, we’re able to just use <code>https://localhost:5001</code>. We do so by by updating the <code>Configure</code> method this time, in the same <code>Startup.cs</code> file that we’ve been working on:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
<span style="color:#000;background-color:#fdd">- app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "VehicleQuotes v1"));
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ app.UseSwaggerUI(c => {
</span><span style="color:#000;background-color:#dfd">+ c.SwaggerEndpoint("/swagger/v1/swagger.json", "VehicleQuotes v1");
</span><span style="color:#000;background-color:#dfd">+ c.RoutePrefix = "";
</span><span style="color:#000;background-color:#dfd">+ });
</span><span style="color:#000;background-color:#dfd"></span> }
</code></pre></div><p>The magic is done by the <code>c.RoutePrefix = "";</code> line which makes it so there’s no need to put any prefix in order to access the auto generated Swagger UI.</p>
<p>Try it out. Do <code>dotnet run</code> and navigate to <code>https://localhost:5001</code> and you should see the Swagger UI there.</p>
<h3 id="building-the-application">Building the application</h3>
<h4 id="creating-model-entities-migrations-and-updating-the-database">Creating model entities, migrations and updating the database</h4>
<p>Alright, with all that configuration out of the way, let’s implement some of our actual application logic now. Refer back to our data model. We’ll start by defining our three simplest tables: <code>makes</code>, <code>sizes</code> and <code>body_types</code>. With EF Core, we define tables via so-called <a href="https://en.wikipedia.org/wiki/Plain_old_CLR_object">POCO</a> entities, which are simple C# classes with some properties. The classes become tables and the properties become the tables’ fields. Instances of these classes represent records in the database.</p>
<p>So, create a new <code>Models</code> directory in our project’s root and add these three files:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/BodyType.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">BodyType</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Name { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/Make.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">Make</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Name { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/Size.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">Size</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Name { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Now, we add three corresponding <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbset-1?view=efcore-5.0"><code>DbSet</code></a>s to our <code>DbContext</code> in <code>Data/VehicleQuoteContext.cs</code>. Here’s the diff:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">using Microsoft.EntityFrameworkCore;
<span style="color:#000;background-color:#dfd">+using VehicleQuotes.Models;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes
{
public class VehicleQuotesContext : DbContext
{
public VehicleQuotesContext (DbContextOptions<VehicleQuotesContext> options)
: base(options)
{
}
<span style="color:#000;background-color:#dfd">+ public DbSet<Make> Makes { get; set; }
</span><span style="color:#000;background-color:#dfd">+ public DbSet<Size> Sizes { get; set; }
</span><span style="color:#000;background-color:#dfd">+ public DbSet<BodyType> BodyTypes { get; set; }
</span><span style="color:#000;background-color:#dfd"></span> }
}
</code></pre></div><p>This is how we tell EF Core to build tables in our database for our entities. You’ll see later how we use those <code>DbSet</code>s to access the data in those tables. For now, let’s create a <a href="https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli">migration</a> script that we can later run to apply changes to our database. Run the following to have EF Core create it for us:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef migrations add AddLookupTables
</code></pre></div><p>Now take a look at the newly created <code>Migrations</code> directory. It contains a few new files, but the one we care about right now is <code>Migrations/{TIMESTAMP}_AddLookupTables.cs</code>. In its <code>Up</code> method, it’s got some code that will modify the database structure when run. The EF Core tooling has inspected our project, identified the new entities, and automatically generated a migration script for us that creates tables for them. Notice also how the tables and fields use the snake case naming convention, just as we specified with the call to <code>UseSnakeCaseNamingConvention</code> in <code>Startup.cs</code>.</p>
<p>Now, to actually run the migration script and apply the changes to the database, we do:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef database update
</code></pre></div><p>That command inspects our project to find any migrations that haven’t been run yet, and applies them. In this case, we only have one, so that’s what it runs. Look at the output in the console to see it working its magic step by step:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef database update
Build started...
Build succeeded.
warn: Microsoft.EntityFrameworkCore.Model.Validation[10400]
Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
...
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [<span style="color:#369">Parameters</span>=[], <span style="color:#369">CommandType</span>=<span style="color:#d20;background-color:#fff0f0">'Text'</span>, <span style="color:#369">CommandTimeout</span>=<span style="color:#d20;background-color:#fff0f0">'30'</span>]
CREATE TABLE sizes (
id integer GENERATED BY DEFAULT AS IDENTITY,
name text NULL,
CONSTRAINT pk_sizes PRIMARY KEY (id)
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [<span style="color:#369">Parameters</span>=[], <span style="color:#369">CommandType</span>=<span style="color:#d20;background-color:#fff0f0">'Text'</span>, <span style="color:#369">CommandTimeout</span>=<span style="color:#d20;background-color:#fff0f0">'30'</span>]
INSERT INTO <span style="color:#d20;background-color:#fff0f0">"__EFMigrationsHistory"</span> (migration_id, product_version)
VALUES (<span style="color:#d20;background-color:#fff0f0">'20210625212939_AddLookupTables'</span>, <span style="color:#d20;background-color:#fff0f0">'5.0.7'</span>);
Done.
</code></pre></div><p>Notice how it warns us about potential exposure of sensitive data because of that <code>EnableSensitiveDataLogging</code> option we opted into in <code>Startup.cs</code>. Also, EF Core related logs are extra verbose showing all database operations because of another configuration option that we applied there: the <code>UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()))</code> one.</p>
<p>You can connect to the database with the <code>psql</code> command line client and see that the changes took effect:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ psql -h localhost -U vehicle_quote
...
<span style="color:#369">vehicle_quote</span>=<span style="color:#888"># \c vehicle_quote </span>
psql (12.7 (Ubuntu 12.7-0ubuntu0.20.10.1), server 13.2 (Debian 13.2-1.pgdg100+1))
You are now connected to database <span style="color:#d20;background-color:#fff0f0">"vehicle_quote"</span> as user <span style="color:#d20;background-color:#fff0f0">"vehicle_quote"</span>.
<span style="color:#369">vehicle_quote</span>=<span style="color:#888"># \dt</span>
List of relations
Schema | Name | Type | Owner
--------+-----------------------+-------+---------------
public | __EFMigrationsHistory | table | vehicle_quote
public | body_types | table | vehicle_quote
public | makes | table | vehicle_quote
public | sizes | table | vehicle_quote
(<span style="color:#00d;font-weight:bold">4</span> rows)
<span style="color:#369">vehicle_quote</span>=<span style="color:#888"># \d makes</span>
Table <span style="color:#d20;background-color:#fff0f0">"public.makes"</span>
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+----------------------------------
id | integer | | not null | generated by default as identity
name | text | | |
Indexes:
<span style="color:#d20;background-color:#fff0f0">"pk_makes"</span> PRIMARY KEY, btree (id)
</code></pre></div><p>There are our tables in all their normalized, snake-cased glory. The <code>__EFMigrationsHistory</code> table is used internally by EF Core to keep track of which migrations have been applied.</p>
<h4 id="creating-controllers-for-cruding-our-tables">Creating controllers for CRUDing our tables</h4>
<p>Now that we have that, let’s add a few endpoints to support basic CRUD of those tables. We can use the <code>dotnet-aspnet-codegenerator</code> scaffolding tool that we installed earlier. For the three tables that we have, we would do:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet aspnet-codegenerator controller <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -name MakesController <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -m Make <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -dc VehicleQuotesContext <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -async <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -api <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -outDir Controllers
$ dotnet aspnet-codegenerator controller <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -name BodyTypesController <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -m BodyType <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -dc VehicleQuotesContext <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -async <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -api <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -outDir Controllers
$ dotnet aspnet-codegenerator controller <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -name SizesController <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -m Size <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -dc VehicleQuotesContext <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -async <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -api <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -outDir Controllers
</code></pre></div><p>Those commands tell the scaffolding tool to create new controllers that:</p>
<ol>
<li>Are named as given by the <code>-name</code> option.</li>
<li>Use the model class specified in the <code>-m</code> option.</li>
<li>Use our <code>VehicleQuotesContext</code> to talk to the database. As per the <code>-dc</code> option.</li>
<li>Define the methods using <code>async</code>/<code>await</code> syntax. Given by the <code>-async</code> option.</li>
<li>Are API controllers. Specified by the <code>-api</code> option.</li>
<li>Are created in the <code>Controllers</code> directory. Via the <code>-outDir</code> option.</li>
</ol>
<p>Explore the new files that got created in the <code>Controllers</code> directory: <code>MakesController.cs</code>, <code>BodyTypesController.cd</code> and <code>SizesController.cs</code>. The controllers have been generated with the necessary <a href="https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/aspnet-mvc-controllers-overview-cs#understanding-controller-actions">Action Methods</a> to fetch, create, update and delete their corresponding entities. Try <code>dotnet run</code> and navigate to <code>https://localhost:5001</code> to see the new endpoints in the Swagger UI:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/swagger-lookup-tables.png" alt="Swagger UI with lookup tables"></p>
<p>Try it out! You can interact with each of the endpoints from the Swagger UI and it all works as you’d expect.</p>
<h4 id="adding-unique-constraints-via-indexes">Adding unique constraints via indexes</h4>
<p>Ok, our app is coming along well. Right now though, there’s an issue with the tables that we’ve created. It’s possible to create vehicle makes with the same name. The same is true for body types and sizes. This doesn’t make much sense for these tables. So let’s fix that by adding a uniqueness constraint. We can do it by creating a unique database index using EF Core’s <code>Index</code> attribute. For example, we can modify our <code>Models/Make.cs</code> like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#000;background-color:#dfd">+using Microsoft.EntityFrameworkCore;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.Models
{
<span style="color:#000;background-color:#dfd">+ [Index(nameof(Name), IsUnique = true)]
</span><span style="color:#000;background-color:#dfd"></span> public class Make
{
public int ID { get; set; }
public string Name { get; set; }
}
}
</code></pre></div><p>In fact, do the same for our other entities in <code>Models/BodyType.cs</code> and <code>Models/Size.cs</code>. Don’t forget the <code>using Microsoft.EntityFrameworkCore</code> statement.</p>
<p>With that, we can create a new migration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef migrations add AddUniqueIndexesToLookupTables
</code></pre></div><p>That will result in a new migration script in <code>Migrations/{TIMESTAMP}_AddUniqueIndexesToLookupTables.cs</code>. Its <code>Up</code> method looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">void</span> Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: <span style="color:#d20;background-color:#fff0f0">"ix_sizes_name"</span>,
table: <span style="color:#d20;background-color:#fff0f0">"sizes"</span>,
column: <span style="color:#d20;background-color:#fff0f0">"name"</span>,
unique: <span style="color:#080;font-weight:bold">true</span>);
migrationBuilder.CreateIndex(
name: <span style="color:#d20;background-color:#fff0f0">"ix_makes_name"</span>,
table: <span style="color:#d20;background-color:#fff0f0">"makes"</span>,
column: <span style="color:#d20;background-color:#fff0f0">"name"</span>,
unique: <span style="color:#080;font-weight:bold">true</span>);
migrationBuilder.CreateIndex(
name: <span style="color:#d20;background-color:#fff0f0">"ix_body_types_name"</span>,
table: <span style="color:#d20;background-color:#fff0f0">"body_types"</span>,
column: <span style="color:#d20;background-color:#fff0f0">"name"</span>,
unique: <span style="color:#080;font-weight:bold">true</span>);
}
</code></pre></div><p>As you can see, new unique indexes are being created on the tables and fields that we specified. Like before, apply the changes to the database structure with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef database update
</code></pre></div><p>Now if you try to create, for example, a vehicle make with a repeated name, you’ll get an error. Try doing so by <code>POST</code>ing to <code>/api/Makes</code> via the Swagger UI:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/unique-constraint-violation.png" alt="Unique constraint violation"></p>
<h4 id="responding-with-specific-http-error-codes-409-conflict">Responding with specific HTTP error codes (409 Conflict)</h4>
<p>The fact that we can now enforce unique constraints is all well and good. But the error scenario is not very user friendly. Instead of returning a “500 Internal Server Error” status code with a wall of text, we should be responding with something more sensible. Maybe a “409 Conflict” would be more appropriate for this kind of error. We can easily update our controllers to handle that scenario. What we need to do is update the methods that handle the <code>POST</code> and <code>PUT</code> endpoints so that they catch the <code>Microsoft.EntityFrameworkCore.DbUpdateException</code> exception and return the proper response. Here’s how we would do it for the <code>MakesController</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">// ...
namespace VehicleQuotes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MakesController : ControllerBase
{
// ...
[HttpPut("{id}")]
public async Task<IActionResult> PutMake(int id, Make make)
{
// ...
try
{
await _context.SaveChangesAsync();
}
// ...
<span style="color:#000;background-color:#dfd">+ catch (Microsoft.EntityFrameworkCore.DbUpdateException)
</span><span style="color:#000;background-color:#dfd">+ {
</span><span style="color:#000;background-color:#dfd">+ return Conflict();
</span><span style="color:#000;background-color:#dfd">+ }
</span><span style="color:#000;background-color:#dfd"></span>
return NoContent();
}
[HttpPost]
public async Task<ActionResult<Make>> PostMake(Make make)
{
_context.Makes.Add(make);
<span style="color:#000;background-color:#fdd">- await _context.SaveChangesAsync();
</span><span style="color:#000;background-color:#fdd"></span>
<span style="color:#000;background-color:#dfd">+ try
</span><span style="color:#000;background-color:#dfd">+ {
</span><span style="color:#000;background-color:#dfd">+ await _context.SaveChangesAsync();
</span><span style="color:#000;background-color:#dfd">+ }
</span><span style="color:#000;background-color:#dfd">+ catch (Microsoft.EntityFrameworkCore.DbUpdateException)
</span><span style="color:#000;background-color:#dfd">+ {
</span><span style="color:#000;background-color:#dfd">+ return Conflict();
</span><span style="color:#000;background-color:#dfd">+ }
</span><span style="color:#000;background-color:#dfd"></span>
return CreatedAtAction("GetMake", new { id = make.ID }, make);
}
// ...
}
}
</code></pre></div><p>Go ahead and do the same for the other two controllers, and try again to POST a repeated make name via the Swagger UI. You should see this now instead:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/http-409-conflict.png" alt="HTTP 409 Conflict"></p>
<p>Much better now, don’t you think?</p>
<h4 id="adding-a-more-complex-entity-to-the-model">Adding a more complex entity to the model</h4>
<p>Now let’s work on an entity that’s a little bit more complex: the one we will use to represent vehicle models.</p>
<p>For this entity, we don’t want our API to be as low level as the one for the other three, where it was basically a thin wrapper over database tables. We want it to be a little bit more abstract and not expose the entire database structure verbatim.</p>
<p>Refer back to the data model. We’ll add <code>models</code>, <code>model_styles</code> and <code>model_style_years</code>. Let’s start by adding the following classes:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/Model.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Collections.Generic</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">Model</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Name { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> MakeID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> Make Make { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> ICollection<ModelStyle> ModelStyles { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/ModelStyle.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Collections.Generic</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ModelStyle</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ModelID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> BodyTypeID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> SizeID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> Model Model { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> BodyType BodyType { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> Size Size { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> ICollection<ModelStyleYear> ModelStyleYears { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/ModelStyleYear.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ModelStyleYear</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Year { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ModelStyleID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> ModelStyle ModelStyle { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Notice how some of these entities now include properties whose types are other entities. Some of them are collections even. These are called Navigation Properties and are how we tell EF Core that our entities are related to one another. These will result in foreign keys being created in the database.</p>
<p>Take the <code>Model</code> entity for example. It has a property <code>Make</code> of type <code>Make</code>. It also has a <code>MakeID</code> property of type <code>int</code>. EF Core sees this and figures out that there’s a relation between the <code>makes</code> and <code>models</code> tables. Specifically, that <code>models</code> have a <code>make</code>. A many-to-one relation where the <code>models</code> table stores a foreign key to the <code>makes</code> table.</p>
<p>Similarly, the <code>Model</code> entity has a <code>ModelStyles</code> property of type <code>ICollection<ModelStyleYear></code>. This tells EF Core that <code>models</code> have many <code>model_styles</code>. This one is a one-to-many relation from the perspective of the <code>models</code> table. The foreign key lives in the <code>model_styles</code> table and points back to <code>models</code>.</p>
<blockquote>
<p>The <a href="https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#single-navigation-property-1">official documentation</a> is a great resource to learn more details about how relationships work in EF Core.</p>
</blockquote>
<p>After that, same as before, we have to add the corresponding <code>DbSet</code>s to our <code>DbContext</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">// Data/VehicleQuotesContext.cs
// ...
namespace VehicleQuotes
{
public class VehicleQuotesContext : DbContext
{
// ...
<span style="color:#000;background-color:#dfd">+ public DbSet<Model> Models { get; set; }
</span><span style="color:#000;background-color:#dfd">+ public DbSet<ModelStyle> ModelStyles { get; set; }
</span><span style="color:#000;background-color:#dfd">+ public DbSet<ModelStyleYear> ModelStyleYears { get; set; }
</span><span style="color:#000;background-color:#dfd"></span> }
}
</code></pre></div><p>Don’t forget the migration script. First create it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef migrations add AddVehicleModelTables
</code></pre></div><p>And then apply it:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef database update
</code></pre></div><h4 id="adding-composite-unique-indexes">Adding composite unique indexes</h4>
<p>These vehicle model related tables also need some uniqueness enforcement. This time, however, the unique keys are composite. Meaning that they involve multiple fields. For vehicle models, for example, it makes no sense to have multiple records with the same make and name. But it does make sense to have multiple models with the same name, as long as they belong to different makes. We can solve for that with a composite index. Here’s how we create one of those in <code>Model</code> with EF Core:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">using System.Collections.Generic;
<span style="color:#000;background-color:#dfd">+using Microsoft.EntityFrameworkCore;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.Models
{
<span style="color:#000;background-color:#dfd">+ [Index(nameof(Name), nameof(MakeID), IsUnique = true)]
</span><span style="color:#000;background-color:#dfd"></span> public class Model
{
// ...
}
}
</code></pre></div><p>Very similar to what we did with the <code>Make</code>, <code>BodyType</code>, and <code>Size</code> entities. The only difference is that this time we included multiple fields in the parameters for the <code>Index</code> attribute.</p>
<p>We should do the same for <code>ModelStyle</code> and <code>ModelStyleYear</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">using System.Collections.Generic;
<span style="color:#000;background-color:#dfd">+using Microsoft.EntityFrameworkCore;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.Models
{
<span style="color:#000;background-color:#dfd">+ [Index(nameof(ModelID), nameof(BodyTypeID), nameof(SizeID), IsUnique = true)]
</span><span style="color:#000;background-color:#dfd"></span> public class ModelStyle
{
// ...
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#000;background-color:#dfd">+using Microsoft.EntityFrameworkCore;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.Models
{
<span style="color:#000;background-color:#dfd">+ [Index(nameof(Year), nameof(ModelStyleID), IsUnique = true)]
</span><span style="color:#000;background-color:#dfd"></span> public class ModelStyleYear
{
// ...
}
}
</code></pre></div><p>Don’t forget the migrations:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef migrations add AddUniqueIndexesForVehicleModelTables
$ dotnet ef database update
</code></pre></div><h4 id="adding-controllers-with-custom-routes">Adding controllers with custom routes</h4>
<p>Our data model dictates that vehicle models belong in a make. In other words, a vehicle model has no meaning by itself. It only has meaning within the context of a make. Ideally, we want our API routes to reflect this concept. In other words, instead of URLs for models to look like this: <code>/api/Models/{id}</code>; we’d rather them look like this: <code>/api/Makes/{makeId}/Models/{modelId}</code>. Let’s go ahead and scaffold a controller for this entity:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet aspnet-codegenerator controller <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -name ModelsController <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -m Model <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -dc VehicleQuotesContext <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -async <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -api <span style="color:#04d;background-color:#fff0f0">\
</span><span style="color:#04d;background-color:#fff0f0"></span> -outDir Controllers
</code></pre></div><p>Now let’s change the resulting <code>Controllers/ModelsController.cs</code> to use the URL structure that we want. To do so, we modify the <code>Route</code> attribute that’s applied to the <code>ModelsController</code> class to this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[Route("api/Makes/{makeId}/[controller]</span>/<span style="color:#d20;background-color:#fff0f0">")]
</span></code></pre></div><p>Do a <code>dotnet run</code> and take a peek at the Swagger UI on <code>https://localhost:5001</code> to see what the <code>Models</code> endpoint routes look like now:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/nested-routes.png" alt="Nested routes"></p>
<p>The vehicle model routes are now nested within makes, just like we wanted.</p>
<p>Of course, this is just eye candy for now. We need to actually use this new <code>makeId</code> parameter for the logic in the endpoints. For example, one would expect a <code>GET</code> to <code>/api/Makes/1/Models</code> to return all the vehicle models that belong to the make with <code>id</code> 1. But right now, all vehicle models are returned regardless. All other endpoints behave similarly, there’s no limit to the operations on the vehicle models. The given <code>makeId</code> is not taken into consideration at all.</p>
<p>Let’s update the <code>ModelsController</code>’s <code>GetModels</code> method (which is the one that handles the <code>GET /api/Makes/{makeId}/Models</code> endpoint) to behave like one would expect. It should look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpGet]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<IEnumerable<Model>>> GetModels([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId)
{
<span style="color:#888;font-weight:bold">var</span> make = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Makes.FindAsync(makeId);
<span style="color:#080;font-weight:bold">if</span> (make == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Models.Where(m => m.MakeID == makeId).ToListAsync();
}
</code></pre></div><p>See how we’ve included a new parameter to the method: <code>[FromRoute] int makeId</code>. This <code>[FromRoute]</code> <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/">attribute</a> is how we tell ASP.NET Core that this endpoint will use that <code>makeId</code> parameter coming from the URL route. Then, we use our <code>DbContext</code> to try to find the make that corresponds to the given identifier. This is done in <code>_context.Makes.FindAsync(makeId)</code>. Then, if we can’t find the given make, we return a <code>404 Not Found</code> HTTP status code with the <code>return NotFound();</code> line. Finally, we query the <code>models</code> table for all the records whose <code>make_id</code> matches the given parameter. That’s done in the last line of the method.</p>
<blockquote>
<p>We have access to the <code>DbContext</code> because it has been injected as a dependency into the controller via its constructor by the framework.</p>
</blockquote>
<blockquote>
<p><a href="https://docs.microsoft.com/en-us/ef/core/querying/">The official documentation</a> is a great resource to learn about all the possibilities when querying data with EF Core.</p>
</blockquote>
<p>Let’s update the <code>GetModel</code> method, which handles the <code>GET /api/Makes/{makeId}/Models/{id}</code> endpoint, similarly.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">[HttpGet("{id}")]
<span style="color:#000;background-color:#fdd">-public async Task<ActionResult<Model>> GetModel(int id)
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+public async Task<ActionResult<Model>> GetModel([FromRoute] int makeId, int id)
</span><span style="color:#000;background-color:#dfd"></span>{
<span style="color:#000;background-color:#fdd">- var model = await _context.Models.FindAsync(id);
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ var model = await _context.Models.FirstOrDefaultAsync(m =>
</span><span style="color:#000;background-color:#dfd">+ m.MakeID == makeId && m.ID == id
</span><span style="color:#000;background-color:#dfd">+ );
</span><span style="color:#000;background-color:#dfd"></span>
if (model == null)
{
return NotFound();
}
return model;
}
</code></pre></div><p>We’ve once again included the <code>makeId</code> as a parameter to the method and modified the EF Core query to use both the make ID and the vehicle model ID when looking for the record.</p>
<p>And that’s the gist of it. Other methods would need to be updated similarly. The next section will include these methods in their final form, so I won’t go through each one of them here.</p>
<h4 id="using-resource-models-as-dtos-for-controllers">Using resource models as DTOs for controllers</h4>
<p>Now, I did say at the beginning that we wanted the vehicle model endpoint to be a bit more abstract. Right now it’s operating directly over the EF Core entities and our table. As a result, creating new vehicle models via the <code>POST /api/Makes/{makeId}/Models</code> endpoint is a pain. Take a look at the Swagger UI request schema for that endpoint:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/raw-model-request-schema.png" alt="Raw model request schema"></p>
<p>This is way too much. Let’s make it a little bit more user friendly by making it more abstract.</p>
<p>To do that, we will introduce what I like to call a Resource Model (or <a href="https://martinfowler.com/eaaCatalog/dataTransferObject.html">DTO, Data Transfer Object</a>, or View Model). This is a class whose only purpose is to streamline the API contract of the endpoint by defining a set of fields that clients will use to make requests and interpret responses. Something that’s simpler than our actual database structure, but still captures all the information that’s important for our application. We will update the <code>ModelsController</code> so that it’s able to receive objects of this new class as requests, operate on them, translate them to our EF Core entities and actual database records, and return them as a response. The hope is that, by hiding the details of our database structure, we make it easier for clients to interact with our API.</p>
<p>So let’s create a new <code>ResourceModels</code> directory in our project’s root and add these two classes:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// ResourceModels/ModelSpecification.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ModelSpecification</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Name { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> ModelSpecificationStyle[] Styles { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// ResourceModels/ModelSpecificationStyle.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ModelSpecificationStyle</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> BodyType { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Size { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span>[] Years { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>Thanks to these two, instead of that mess from above, clients <code>POST</code>ing to <code>/api/Makes/{makeId}/Models</code> will be able to use a request body like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"name"</span>: <span style="color:#d20;background-color:#fff0f0">"string"</span>,
<span style="color:#b06;font-weight:bold">"styles"</span>: [
{
<span style="color:#b06;font-weight:bold">"bodyType"</span>: <span style="color:#d20;background-color:#fff0f0">"string"</span>,
<span style="color:#b06;font-weight:bold">"size"</span>: <span style="color:#d20;background-color:#fff0f0">"string"</span>,
<span style="color:#b06;font-weight:bold">"years"</span>: [
<span style="color:#d20;background-color:#fff0f0">"string"</span>
]
}
]
}
</code></pre></div><p>Which is much simpler. We have the vehicle model name and an array of styles. Each style has a body type and a size, which we can specify by their names because those are unique keys. We don’t need their integer IDs (i.e. primary keys) in order to find to them. Then, each style has an array of strings that contain the years in which those styles are available for that model. The make is part of the URL already, so we don’t need to also specify it in the request payload.</p>
<p>Let’s update our <code>ModelsController</code> to use these Resource Models instead of the <code>Model</code> EF Core entity. Be sure to include the namespace where the Resource Models are defined by adding the following using statement: <code>using VehicleQuotes.ResourceModels;</code>. Now, let’s update the <code>GetModels</code> method (which handles the <code>GET /api/Makes/{makeId}/Models</code> endpoint) so that it looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpGet]</span>
<span style="color:#888">// Return a collection of `ModelSpecification`s and expect a `makeId` from the URL.
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<IEnumerable<ModelSpecification>>> GetModels([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId)
{
<span style="color:#888">// Look for the make identified by `makeId`.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> make = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Makes.FindAsync(makeId);
<span style="color:#888">// If we can't find the make, then we return a 404.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (make == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#888">// Build a query to fetch the relevant records from the `models` table and
</span><span style="color:#888"></span> <span style="color:#888">// build `ModelSpecification` with the data.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> modelsToReturn = <span style="color:#00d;font-weight:bold">_</span>context.Models
.Where(m => m.MakeID == makeId)
.Select(m => <span style="color:#080;font-weight:bold">new</span> ModelSpecification {
ID = m.ID,
Name = m.Name,
Styles = m.ModelStyles.Select(ms => <span style="color:#080;font-weight:bold">new</span> ModelSpecificationStyle {
BodyType = ms.BodyType.Name,
Size = ms.Size.Name,
Years = ms.ModelStyleYears.Select(msy => msy.Year).ToArray()
}).ToArray()
});
<span style="color:#888">// Execute the query and respond with the results.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> modelsToReturn.ToListAsync();
}
</code></pre></div><p>The first thing that we changed was the return type. Instead of <code>Task<ActionResult<IEnumerable<Model>>></code>, the method now returns <code>Task<ActionResult<IEnumerable<ModelSpecification>>></code>. We’re going to use our new Resource Models as these endpoints’ contract, so we need to make sure we are returning those. Next, we considerably changed the LINQ expression that searches the database for the vehicle model records we want. The filtering logic (given by the <code>Where</code>) is the same. That is, we’re still searching for vehicle models within the given make ID. What we changed was the projection logic in the <code>Select</code>. Our Action Method now returns a collection of <code>ModelSpecification</code> objects, so we updated the <code>Select</code> to produce such objects, based on the records from the <code>models</code> table that match our search criteria. We build <code>ModelSpecification</code>s using the data coming from <code>models</code> records and their related <code>model_styles</code> and <code>model_style_years</code>. Finally, we asynchronously execute the query to fetch the data from the database and return it.</p>
<p>Next, let’s move on to the <code>GetModel</code> method, which handles the <code>GET /api/Makes/{makeId}/Models/{id}</code> endpoint. This is what it should look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpGet("{id}")]</span>
<span style="color:#888">// Return a `ModelSpecification`s and expect `makeId` and `id` from the URL.
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<ModelSpecification>> GetModel([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId, [FromRoute] <span style="color:#888;font-weight:bold">int</span> id)
{
<span style="color:#888">// Look for the model specified by the given identifiers and also load
</span><span style="color:#888"></span> <span style="color:#888">// all related data that we care about for this method.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> model = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Models
.Include(m => m.ModelStyles).ThenInclude(ms => ms.BodyType)
.Include(m => m.ModelStyles).ThenInclude(ms => ms.Size)
.Include(m => m.ModelStyles).ThenInclude(ms => ms.ModelStyleYears)
.FirstOrDefaultAsync(m => m.MakeID == makeId && m.ID == id);
<span style="color:#888">// If we couldn't find it, respond with a 404.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (model == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#888">// Use the fetched data to construct a `ModelSpecification` to use in the response.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> ModelSpecification {
ID = model.ID,
Name = model.Name,
Styles = model.ModelStyles.Select(ms => <span style="color:#080;font-weight:bold">new</span> ModelSpecificationStyle {
BodyType = ms.BodyType.Name,
Size = ms.Size.Name,
Years = ms.ModelStyleYears.Select(msy => msy.Year).ToArray()
}).ToArray()
};
}
</code></pre></div><p>Same as before, we changed the return type of the method to be <code>ModelSpecification</code>. Then, we modified the query so that it loads all the related data for the <code>Model</code> entity via its navigation properties. That’s what the <code>Include</code> and <code>ThenInclude</code> calls do. We need this data loaded because we use it in the method’s return statement to build the <code>ModelSpecification</code> that will be included in the response. The logic to build it is very similar to that of the previous method.</p>
<blockquote>
<p>You can learn more about the various available approaches for loading data with EF Core in <a href="https://docs.microsoft.com/en-us/ef/core/querying/related-data/">the official documentation</a>.</p>
</blockquote>
<p>Next is the <code>PUT /api/Makes/{makeId}/Models/{id}</code> endpoint, handled by the <code>PutModel</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpPut("{id}")]</span>
<span style="color:#888">// Expect `makeId` and `id` from the URL and a `ModelSpecification` from the request payload.
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<IActionResult> PutModel([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId, <span style="color:#888;font-weight:bold">int</span> id, ModelSpecification model)
{
<span style="color:#888">// If the id in the URL and the request payload are different, return a 400.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (id != model.ID)
{
<span style="color:#080;font-weight:bold">return</span> BadRequest();
}
<span style="color:#888">// Obtain the `models` record that we want to update. Include any related
</span><span style="color:#888"></span> <span style="color:#888">// data that we want to update as well.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> modelToUpdate = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Models
.Include(m => m.ModelStyles)
.FirstOrDefaultAsync(m => m.MakeID == makeId && m.ID == id);
<span style="color:#888">// If we can't find the record, then return a 404.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (modelToUpdate == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#888">// Update the record with what came in the request payload.
</span><span style="color:#888"></span> modelToUpdate.Name = model.Name;
<span style="color:#888">// Build EF Core entities based on the incoming Resource Model object.
</span><span style="color:#888"></span> modelToUpdate.ModelStyles = model.Styles.Select(style => <span style="color:#080;font-weight:bold">new</span> ModelStyle {
BodyType = <span style="color:#00d;font-weight:bold">_</span>context.BodyTypes.Single(bodyType => bodyType.Name == style.BodyType),
Size = <span style="color:#00d;font-weight:bold">_</span>context.Sizes.Single(size => size.Name == style.Size),
ModelStyleYears = style.Years.Select(year => <span style="color:#080;font-weight:bold">new</span> ModelStyleYear {
Year = year
}).ToList()
}).ToList();
<span style="color:#080;font-weight:bold">try</span>
{
<span style="color:#888">// Try saving the changes. This will run the UPDATE statement in the database.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.SaveChangesAsync();
}
<span style="color:#080;font-weight:bold">catch</span> (Microsoft.EntityFrameworkCore.DbUpdateException)
{
<span style="color:#888">// If there's an error updating, respond accordingly.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> Conflict();
}
<span style="color:#888">// Finally return a 204 if everything went well.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> NoContent();
}
</code></pre></div><p>The purpose of this endpoint is to update existing resources. So, it receives a representation of said resource as a parameter that comes from the request body. Before, it expected an instance of the <code>Model</code> entity, but now, we’ve changed it to receive a <code>ModelSpecification</code>. The rest of the method is your usual structure of first obtaining the record to update by the given IDs, then changing its values according to what came in as a parameter, and finally, saving the changes.</p>
<p>You probably get the idea by now: since the API is using the Resource Model, we need to change input and output values for the methods and run some logic to translate between Resource Model objects and Data Model objects that EF Core can understand so that it can perform its database operations.</p>
<p>That said, here’s what the <code>PostModel</code> Action Method, handler of the <code>POST /api/Makes/{makeId}/Models</code> endpoint, should look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpPost]</span>
<span style="color:#888">// Return a `ModelSpecification`s and expect `makeId` from the URL and a `ModelSpecification` from the request payload.
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<ModelSpecification>> PostModel([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId, ModelSpecification model)
{
<span style="color:#888">// First, try to find the make specified by the incoming `makeId`.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> make = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Makes.FindAsync(makeId);
<span style="color:#888">// Respond with 404 if not found.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (make == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#888">// Build out a new `Model` entity, complete with all related data, based on
</span><span style="color:#888"></span> <span style="color:#888">// the `ModelSpecification` parameter.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> modelToCreate = <span style="color:#080;font-weight:bold">new</span> Model {
Make = make,
Name = model.Name,
ModelStyles = model.Styles.Select(style => <span style="color:#080;font-weight:bold">new</span> ModelStyle {
<span style="color:#888">// Notice how we search both body type and size by their name field.
</span><span style="color:#888"></span> <span style="color:#888">// We can do that because their names are unique.
</span><span style="color:#888"></span> BodyType = <span style="color:#00d;font-weight:bold">_</span>context.BodyTypes.Single(bodyType => bodyType.Name == style.BodyType),
Size = <span style="color:#00d;font-weight:bold">_</span>context.Sizes.Single(size => size.Name == style.Size),
ModelStyleYears = style.Years.Select(year => <span style="color:#080;font-weight:bold">new</span> ModelStyleYear {
Year = year
}).ToArray()
}).ToArray()
};
<span style="color:#888">// Add it to the DbContext.
</span><span style="color:#888"></span> <span style="color:#00d;font-weight:bold">_</span>context.Add(modelToCreate);
<span style="color:#080;font-weight:bold">try</span>
{
<span style="color:#888">// Try running the INSERTs.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.SaveChangesAsync();
}
<span style="color:#080;font-weight:bold">catch</span> (Microsoft.EntityFrameworkCore.DbUpdateException)
{
<span style="color:#888">// Return accordingly if an error happens.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> Conflict();
}
<span style="color:#888">// Get back the autogenerated ID of the record we just INSERTed.
</span><span style="color:#888"></span> model.ID = modelToCreate.ID;
<span style="color:#888">// Finally, return a 201 including a location header containing the newly
</span><span style="color:#888"></span> <span style="color:#888">// created resource's URL and the resource itself in the response payload.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> CreatedAtAction(
nameof(GetModel),
<span style="color:#080;font-weight:bold">new</span> { makeId = makeId, id = model.ID },
model
);
}
</code></pre></div><p>All that should be pretty self explanatory by now. Moving on to the <code>DeleteModel</code> method which handles the <code>DELETE /api/Makes/{makeId}/Models/{id}</code> endpoint:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#369">[HttpDelete("{id}")]</span>
<span style="color:#888">// Expect `makeId` and `id` from the URL.
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<IActionResult> DeleteModel([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId, <span style="color:#888;font-weight:bold">int</span> id)
{
<span style="color:#888">// Try to find the record identified by the ids from the URL.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> model = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Models.FirstOrDefaultAsync(m => m.MakeID == makeId && m.ID == id);
<span style="color:#888">// Respond with a 404 if we can't find it.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (model == <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">return</span> NotFound();
}
<span style="color:#888">// Mark the entity for removal and run the DELETE.
</span><span style="color:#888"></span> <span style="color:#00d;font-weight:bold">_</span>context.Models.Remove(model);
<span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.SaveChangesAsync();
<span style="color:#888">// Respond with a 204.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> NoContent();
}
</code></pre></div><p>And that’s all for that controller. Hopefully that demonstrated what it looks like to have endpoints that operate using objects other than the EF Core entities. Fire up the app with <code>dotnet run</code> and explore the Swagger UI and you’ll see the changes that we’ve made reflected in there. Try it out. Try CRUDing some vehicle models. And don’t forget to take a look at our POST endpoint specification which looks much more manageable now:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/post-model-endpoint.png" alt="POST Models endpoint"></p>
<p>Which means that you can send in something like this, for example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"name"</span>: <span style="color:#d20;background-color:#fff0f0">"Corolla"</span>,
<span style="color:#b06;font-weight:bold">"styles"</span>: [
{
<span style="color:#b06;font-weight:bold">"bodyType"</span>: <span style="color:#d20;background-color:#fff0f0">"Sedan"</span>,
<span style="color:#b06;font-weight:bold">"size"</span>: <span style="color:#d20;background-color:#fff0f0">"Compact"</span>,
<span style="color:#b06;font-weight:bold">"years"</span>: [ <span style="color:#d20;background-color:#fff0f0">"2000"</span>, <span style="color:#d20;background-color:#fff0f0">"2001"</span> ]
}
]
}
</code></pre></div><blockquote>
<p>This will work assuming you’ve created at least one make to add the vehicle model to, as well as a body type whose name is <code>Sedan</code> and a size whose name is <code>Compact</code>.</p>
</blockquote>
<blockquote>
<p>There’s also a <code>ModelExists</code> method in that controller which we don’t need anymore. You can delete it.</p>
</blockquote>
<h4 id="validation-using-built-in-data-annotations">Validation using built-in Data Annotations</h4>
<p>Depending on how “creative” you were in the previous section when trying to CRUD models, you may have run into an issue or two regarding the data that’s allowed into our database. We solve that by implementing input validation. In ASP.NET Core, the easiest way to implement validation is via Data Annotation attributes on the entities or other objects that controllers receive as request payloads. So let’s see about adding some validation to our app. Since our <code>ModelsController</code> uses the <code>ModelSpecification</code> and <code>ModelSpecificationStyle</code> Resource Models to talk to clients, let’s start there. Here’s the diff:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#000;background-color:#dfd">+using System.ComponentModel.DataAnnotations;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.ResourceModels
{
public class ModelSpecification
{
public int ID { get; set; }
<span style="color:#000;background-color:#dfd">+ [Required]
</span><span style="color:#000;background-color:#dfd"></span> public string Name { get; set; }
<span style="color:#000;background-color:#dfd">+ [Required]
</span><span style="color:#000;background-color:#dfd"></span> public ModelSpecificationStyle[] Styles { get; set; }
}
}
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><span style="color:#000;background-color:#dfd">+using System.ComponentModel.DataAnnotations;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.ResourceModels
{
public class ModelSpecificationStyle
{
<span style="color:#000;background-color:#dfd">+ [Required]
</span><span style="color:#000;background-color:#dfd"></span> public string BodyType { get; set; }
<span style="color:#000;background-color:#dfd">+ [Required]
</span><span style="color:#000;background-color:#dfd"></span> public string Size { get; set; }
<span style="color:#000;background-color:#dfd">+ [Required]
</span><span style="color:#000;background-color:#dfd">+ [MinLength(1)]
</span><span style="color:#000;background-color:#dfd"></span> public string[] Years { get; set; }
}
}
</code></pre></div><p>And just like that, we get a good amount of functionality. We use the <code>Required</code> and <code>MinLength</code> attributes from the <code>System.ComponentModel.DataAnnotations</code> namespace to specify that some fields are required, and that our <code>Years</code> array needs to contain at least one element. When the app receives a request to the PUT or POST endpoints — which are the ones that expect a <code>ModelSpecification</code> as the payload — validation kicks in. If it fails, the action method is never executed and a 400 status code is returned as a response. Try POSTing to <code>/api/Makes/{makeId}/Models</code> with a payload that violates some of these rules to see for yourself. I tried for example sending this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"name"</span>: <span style="color:#080;font-weight:bold">null</span>,
<span style="color:#b06;font-weight:bold">"styles"</span>: [
{
<span style="color:#b06;font-weight:bold">"bodyType"</span>: <span style="color:#d20;background-color:#fff0f0">"Sedan"</span>,
<span style="color:#b06;font-weight:bold">"size"</span>: <span style="color:#d20;background-color:#fff0f0">"Full size"</span>,
<span style="color:#b06;font-weight:bold">"years"</span>: []
}
]
}
</code></pre></div><p>And I got back a 400 response with this payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"type"</span>: <span style="color:#d20;background-color:#fff0f0">"https://tools.ietf.org/html/rfc7231#section-6.5.1"</span>,
<span style="color:#b06;font-weight:bold">"title"</span>: <span style="color:#d20;background-color:#fff0f0">"One or more validation errors occurred."</span>,
<span style="color:#b06;font-weight:bold">"status"</span>: <span style="color:#00d;font-weight:bold">400</span>,
<span style="color:#b06;font-weight:bold">"traceId"</span>: <span style="color:#d20;background-color:#fff0f0">"00-0fd4f00eeb9f2f458ccefc180fcfba1c-79a618f13218394b-00"</span>,
<span style="color:#b06;font-weight:bold">"errors"</span>: {
<span style="color:#b06;font-weight:bold">"Name"</span>: [
<span style="color:#d20;background-color:#fff0f0">"The Name field is required."</span>
],
<span style="color:#b06;font-weight:bold">"Styles[0].Years"</span>: [
<span style="color:#d20;background-color:#fff0f0">"The field Years must be a string or array type with a minimum length of '1'."</span>
]
}
}
</code></pre></div><p>Pretty neat, huh? With minimal effort, we have some basic validation rules in place and a pretty usable response for when errors occur.</p>
<blockquote>
<p>To learn more about model validation, including all the various validation attributes included in the framework, check the official documentation: <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0">Model validation</a> and <a href="https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-5.0">System.ComponentModel.DataAnnotations Namespace</a>.</p>
</blockquote>
<h4 id="validation-using-custom-attributes">Validation using custom attributes</h4>
<p>Of course, the framework is never going to cover all possible validation scenarios with the built-in attributes. Case in point, it’d be great to validate that the <code>Years</code> array contains values that look like actual years. That is, four-character, digit-only strings. There are no validation attributes for that. So, we need to create our own. Let’s add this file into a new <code>Validations</code> directory:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Validations/ContainsYearsAttribute.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Linq</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Runtime.CompilerServices</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Validation</span>
{
<span style="color:#888">// In the .NET Framework, attribute classes need to have their name suffixed with the word "Attribute".
</span><span style="color:#888"></span> <span style="color:#888">// Validation attributes need to inherit from `System.ComponentModel.DataAnnotations`'s `ValidationAttribute` class
</span><span style="color:#888"></span> <span style="color:#888">// and override the `IsValid` method.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">ContainsYearsAttribute</span> : ValidationAttribute
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#888;font-weight:bold">string</span> propertyName;
<span style="color:#888">// This constructor is called by the framework when the attribute is applied to some member. In this specific
</span><span style="color:#888"></span> <span style="color:#888">// case, we define a `propertyName` parameter annotated with a `CallerMemberName` attribute. This makes it so
</span><span style="color:#888"></span> <span style="color:#888">// the framework sends in the name of the member to which our `ContainsYears` attribute is applied to.
</span><span style="color:#888"></span> <span style="color:#888">// We store the value to use it later when constructing our validation error message.
</span><span style="color:#888"></span> <span style="color:#888">// Check https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute?view=net-5.0
</span><span style="color:#888"></span> <span style="color:#888">// for more info on `CallerMemberName`.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> ContainsYearsAttribute([CallerMemberName] <span style="color:#888;font-weight:bold">string</span> propertyName = <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#080;font-weight:bold">this</span>.propertyName = propertyName;
}
<span style="color:#888">// This method is called by the framework during validation. `value` is the actual value of the field that this
</span><span style="color:#888"></span> <span style="color:#888">// attribute will validate.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> ValidationResult IsValid(<span style="color:#888;font-weight:bold">object</span> <span style="color:#080;font-weight:bold">value</span>, ValidationContext validationContext)
{
<span style="color:#888">// By only applying the validation checks when the value is not null, we make it possible for this
</span><span style="color:#888"></span> <span style="color:#888">// attribute to work on optional fields. In other words, this attribute will skip validation if there is no
</span><span style="color:#888"></span> <span style="color:#888">// value to validate.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">value</span> != <span style="color:#080;font-weight:bold">null</span>)
{
<span style="color:#888">// Check if all the elements of the string array are valid years. Check the `IsValidYear` method below
</span><span style="color:#888"></span> <span style="color:#888">// to see what checks are applied for each of the array elements.
</span><span style="color:#888"></span> <span style="color:#888;font-weight:bold">var</span> isValid = (<span style="color:#080;font-weight:bold">value</span> <span style="color:#080;font-weight:bold">as</span> <span style="color:#888;font-weight:bold">string</span>[]).All(IsValidYear);
<span style="color:#080;font-weight:bold">if</span> (!isValid)
{
<span style="color:#888">// If not, return an error.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> ValidationResult(GetErrorMessage());
}
}
<span style="color:#888">// Return a successful validation result if no errors were detected.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> ValidationResult.Success;
}
<span style="color:#888">// Determines if a given value is valid by making sure it's not null, nor empty, that its length is 4 and that
</span><span style="color:#888"></span> <span style="color:#888">// all its characters are digits.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#888;font-weight:bold">bool</span> IsValidYear(<span style="color:#888;font-weight:bold">string</span> <span style="color:#080;font-weight:bold">value</span>) =>
!String.IsNullOrEmpty(<span style="color:#080;font-weight:bold">value</span>) && <span style="color:#080;font-weight:bold">value</span>.Length == <span style="color:#00d;font-weight:bold">4</span> && <span style="color:#080;font-weight:bold">value</span>.All(Char.IsDigit);
<span style="color:#888">// Builds a user friendly error message which includes the name of the field that this validation attribute has
</span><span style="color:#888"></span> <span style="color:#888">// been applied to.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#888;font-weight:bold">string</span> GetErrorMessage() =>
<span style="color:#d20;background-color:#fff0f0">$"The {propertyName} field must be an array of strings containing four digits."</span>;
}
}
</code></pre></div><p>Check the comments in the code for more details into how that class works. Then, we apply our custom attribute to our <code>ModelSpecificationStyle</code> class in the same way that we applied the built in ones:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">using System.ComponentModel.DataAnnotations;
<span style="color:#000;background-color:#dfd">+using VehicleQuotes.Validation;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.ResourceModels
{
public class ModelSpecificationStyle
{
// ...
[Required]
[MinLength(1)]
<span style="color:#000;background-color:#dfd">+ [ContainsYears]
</span><span style="color:#000;background-color:#dfd"></span> public string[] Years { get; set; }
}
}
</code></pre></div><p>Now do a <code>dotnet run</code> and try to POST to <code>/api/Makes/{makeId}/Models</code> this payload:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"name"</span>: <span style="color:#d20;background-color:#fff0f0">"Rav4"</span>,
<span style="color:#b06;font-weight:bold">"styles"</span>: [
{
<span style="color:#b06;font-weight:bold">"bodyType"</span>: <span style="color:#d20;background-color:#fff0f0">"SUV"</span>,
<span style="color:#b06;font-weight:bold">"size"</span>: <span style="color:#d20;background-color:#fff0f0">"Mid size"</span>,
<span style="color:#b06;font-weight:bold">"years"</span>: [ <span style="color:#d20;background-color:#fff0f0">"not_a_year"</span> ]
}
]
}
</code></pre></div><p>That should make the API respond with this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"type"</span>: <span style="color:#d20;background-color:#fff0f0">"https://tools.ietf.org/html/rfc7231#section-6.5.1"</span>,
<span style="color:#b06;font-weight:bold">"title"</span>: <span style="color:#d20;background-color:#fff0f0">"One or more validation errors occurred."</span>,
<span style="color:#b06;font-weight:bold">"status"</span>: <span style="color:#00d;font-weight:bold">400</span>,
<span style="color:#b06;font-weight:bold">"traceId"</span>: <span style="color:#d20;background-color:#fff0f0">"00-9980325f3e388f48a5975ef382d5b137-2d55da1bb9613e4f-00"</span>,
<span style="color:#b06;font-weight:bold">"errors"</span>: {
<span style="color:#b06;font-weight:bold">"Styles[0].Years"</span>: [
<span style="color:#d20;background-color:#fff0f0">"The Years field must be an array of strings containing four numbers."</span>
]
}
}
</code></pre></div><p>That’s our custom validation attribute doing its job.</p>
<p>There’s another aspect that we could validate using a custom validation attribute. What happens if we try to POST a payload with a body type or size that doesn’t exist? These queries from the <code>PostModel</code> method would throw an <code>InvalidOperationException</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs">BodyType = <span style="color:#00d;font-weight:bold">_</span>context.BodyTypes.Single(bodyType => bodyType.Name == style.BodyType)
</code></pre></div><p>and</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs">Size = <span style="color:#00d;font-weight:bold">_</span>context.Sizes.Single(size => size.Name == style.Size)
</code></pre></div><p>They do so because we used the <code>Single</code> method, which is designed like that. It tries to find a body type or size whose name is the given value, can’t find it, and thus, throws an exception.</p>
<blockquote>
<p>If, for example, we wanted not-founds to return <code>null</code>, we could have used <code>SingleOrDefault</code> instead.</p>
</blockquote>
<p>This unhandled exception results in a response that’s quite unbecoming:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/invalid-operation-exception.png" alt="InvalidOperationException during POST"></p>
<p>So, to prevent that exception and control the error messaging, we need a couple of new validation attributes that go into the <code>body_types</code> and <code>sizes</code> tables and check if the given values exist. Here’s what one would look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Validations/VehicleBodyTypeAttribute.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Linq</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Validation</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">VehicleBodyTypeAttribute</span> : ValidationAttribute
{
<span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> ValidationResult IsValid(<span style="color:#888;font-weight:bold">object</span> <span style="color:#080;font-weight:bold">value</span>, ValidationContext validationContext)
{
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">value</span> == <span style="color:#080;font-weight:bold">null</span>) <span style="color:#080;font-weight:bold">return</span> ValidationResult.Success;
<span style="color:#888;font-weight:bold">var</span> dbContext = validationContext.GetService(<span style="color:#080;font-weight:bold">typeof</span>(VehicleQuotesContext)) <span style="color:#080;font-weight:bold">as</span> VehicleQuotesContext;
<span style="color:#888;font-weight:bold">var</span> bodyTypes = dbContext.BodyTypes.Select(bt => bt.Name).ToList();
<span style="color:#080;font-weight:bold">if</span> (!bodyTypes.Contains(<span style="color:#080;font-weight:bold">value</span>))
{
<span style="color:#888;font-weight:bold">var</span> allowed = String.Join(<span style="color:#d20;background-color:#fff0f0">", "</span>, bodyTypes);
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> ValidationResult(
<span style="color:#d20;background-color:#fff0f0">$"Invalid vehicle body type {value}. Allowed values are {allowed}."</span>
);
}
<span style="color:#080;font-weight:bold">return</span> ValidationResult.Success;
}
}
}
</code></pre></div><blockquote>
<p>You can find the other one here: <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Validations/VehicleSizeAttribute.cs">Validations/VehicleSizeAttribute.cs</a>.</p>
</blockquote>
<p>These two are very similar to one another. The most interesting part is how we use the <code>IsValid</code> method’s second parameter (<code>ValidationContext</code>) to obtain an instance of <code>VehicleQuotesContext</code> that we can use to query the database. The rest should be pretty self-explanatory. These attributes are classes that inherit from <code>System.ComponentModel.DataAnnotations</code>’s <code>ValidationAttribute</code> and implement the <code>IsValid</code> method. The method then checks that the value under scrutiny exists in the corresponding table and if it does not, raises a validation error. The validation error includes a list of all allowed values. They can be applied to our <code>ModelSpecificationStyle</code> class like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">// ...
namespace VehicleQuotes.ResourceModels
{
public class ModelSpecificationStyle
{
[Required]
<span style="color:#000;background-color:#dfd">+ [VehicleBodyType]
</span><span style="color:#000;background-color:#dfd"></span> public string BodyType { get; set; }
[Required]
<span style="color:#000;background-color:#dfd">+ [VehicleSize]
</span><span style="color:#000;background-color:#dfd"></span> public string Size { get; set; }
//...
}
}
</code></pre></div><p>Now, a request like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"name"</span>: <span style="color:#d20;background-color:#fff0f0">"Rav4"</span>,
<span style="color:#b06;font-weight:bold">"styles"</span>: [
{
<span style="color:#b06;font-weight:bold">"bodyType"</span>: <span style="color:#d20;background-color:#fff0f0">"not_a_body_type"</span>,
<span style="color:#b06;font-weight:bold">"size"</span>: <span style="color:#d20;background-color:#fff0f0">"Mid size"</span>,
<span style="color:#b06;font-weight:bold">"years"</span>: [ <span style="color:#d20;background-color:#fff0f0">"2000"</span> ]
}
]
}
</code></pre></div><p>Produces a response like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json">{
<span style="color:#b06;font-weight:bold">"type"</span>: <span style="color:#d20;background-color:#fff0f0">"https://tools.ietf.org/html/rfc7231#section-6.5.1"</span>,
<span style="color:#b06;font-weight:bold">"title"</span>: <span style="color:#d20;background-color:#fff0f0">"One or more validation errors occurred."</span>,
<span style="color:#b06;font-weight:bold">"status"</span>: <span style="color:#00d;font-weight:bold">400</span>,
<span style="color:#b06;font-weight:bold">"traceId"</span>: <span style="color:#d20;background-color:#fff0f0">"00-9ad59a7aff60944ab54c19a73be73cc7-eeabafe03df74e40-00"</span>,
<span style="color:#b06;font-weight:bold">"errors"</span>: {
<span style="color:#b06;font-weight:bold">"Styles[0].BodyType"</span>: [
<span style="color:#d20;background-color:#fff0f0">"Invalid vehicle body type not_a_body_type. Allowed values are Coupe, Sedan, Convertible, Hatchback, SUV, Truck."</span>
]
}
}
</code></pre></div><h4 id="implementing-endpoints-for-quote-rules-and-overrides">Implementing endpoints for quote rules and overrides</h4>
<p>At this point we’ve explored many of the most common features available to us for developing Web APIs. So much so that implementing the next two pieces of functionality for our app doesn’t really introduce any new concepts. So, I wont discuss that here in great detail.</p>
<p>Feel free to browse the source code <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api">on GitHub</a> if you want though. These are the relevant files:</p>
<ul>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Controllers/QuoteOverridesController.cs">Controllers/QuoteOverridesController.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Controllers/QuoteRulesController.cs">Controllers/QuoteRulesController.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Models/QuoteOverride.cs">Models/QuoteOverride.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Models/QuoteRule.cs">Models/QuoteRule.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/ResourceModels/QuoteOverrideSpecification.cs">ResourceModels/QuoteOverrideSpecification.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Validations/FeatureTypeAttribute.cs">Validation/FeatureTypeAttribute.cs</a></li>
<li><a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/Migrations/20210627204444_AddQuoteRulesAndOverridesTables.cs">Migrations/20210627204444_AddQuoteRulesAndOverridesTables.cs</a></li>
</ul>
<p>The <code>FeatureTypeAttribute</code> class is interesting in that it provides another example of a validation attribute. This time is one that makes sure the value being validated is included in an array of strings that’s defined literally in the code.</p>
<p>Other than that, it’s all stuff we’ve already covered: models, migrations, scaffolding controllers, custom routes, resource models, etc.</p>
<p>If you are following along, be sure to add those files and run a <code>dotnet ef database update</code> to apply the migration.</p>
<h4 id="implementing-the-quote-model">Implementing the quote model</h4>
<p>Let’s now start implementing the main capability of our app: calculating quotes for vehicles. Let’s start with the <code>Quote</code> entity. This is what the new <code>Models/Quote.cs</code> file containing the entity class will look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">// Models/Quote.cs
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.ComponentModel.DataAnnotations.Schema</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">Quote</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> ID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#888">// Directly tie this quote record to a specific vehicle that we have
</span><span style="color:#888"></span> <span style="color:#888">// registered in our db, if we have it.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int?</span> ModelStyleYearID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#888">// If we don't have the specific vehicle in our db, then store the
</span><span style="color:#888"></span> <span style="color:#888">// vehicle model details independently.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Year { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Make { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Model { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> BodyTypeID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> SizeID { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> ItMoves { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasAllWheels { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasAlloyWheels { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasAllTires { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasKey { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasTitle { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> RequiresPickup { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasEngine { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasTransmission { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">bool</span> HasCompleteInterior { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">int</span> OfferedQuote { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> <span style="color:#888;font-weight:bold">string</span> Message { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> DateTime CreatedAt { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> ModelStyleYear ModelStyleYear { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> BodyType BodyType { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
<span style="color:#080;font-weight:bold">public</span> Size Size { <span style="color:#080;font-weight:bold">get</span>; <span style="color:#080;font-weight:bold">set</span>; }
}
}
</code></pre></div><p>This should be pretty familiar by now. It’s a plain old class that defines a number of properties, one for each of the fields in the resulting table and a few navigation properties that serve to access related data.</p>
<p>The only aspect worth noting is that we’ve defined the <code>ModelStyleYearID</code> property as a nullable integer (with <code>int?</code>). This is because, like we discussed at the beginning, the foreign key from <code>quotes</code> to <code>vehicle_style_years</code> is actually optional. The reason being that we may receive a quote request for a vehicle that we don’t have registered in our database. We need to be able to support quoting those vehicles too, so if we don’t have the requested vehicle registered, then that foreign key will stay unpopulated and we’ll rely on the other fields (i.e. <code>Year</code>, <code>Make</code>, <code>Model</code>, <code>BodyTypeID</code> and <code>SizeID</code>) to identify the vehicle and calculate the quote for it.</p>
<h4 id="using-dependency-injection">Using Dependency Injection</h4>
<p>So far we’ve been putting a lot of logic in our controllers. That’s generally not ideal, but fine as long as the logic is simple. The problem is that a design like that can quickly become a hindrance for maintainability and testing as our application grows more complex. For the logic that calculates a quote, we’d be better served by implementing it in its own class, outside of the controller that should only care about defining endpoints and handling HTTP concerns. Then, the controller can be given access to that class and delegate to it all the quote calculation logic. Thankfully, ASP.NET Core includes an IoC container by default, which allows us to use Dependency Injection to solve these kinds of problems. Let’s see what that looks like.</p>
<p>For working with quotes, we want to offer two endpoints:</p>
<ol>
<li>A <code>POST api/Quotes</code> that captures the vehicle information, calculates the quote, keeps record of the request, and responds with the calculated value.</li>
<li>A <code>GET api/Quotes</code> that returns all the currently registered quotes in the system.</li>
</ol>
<p>Using the Dependency Injection capabilities, a controller that implements those two could look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Collections.Generic</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Threading.Tasks</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.AspNetCore.Mvc</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Services</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Controllers</span>
{
<span style="color:#369"> [Route("api/[controller]</span><span style="color:#d20;background-color:#fff0f0">")]
</span><span style="color:#d20;background-color:#fff0f0"></span><span style="color:#369"> [ApiController]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">QuotesController</span> : ControllerBase
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> QuoteService <span style="color:#00d;font-weight:bold">_</span>service;
<span style="color:#888">// When intiating the request processing logic, the framework recognizes
</span><span style="color:#888"></span> <span style="color:#888">// that this controller has a dependency on QuoteService and expects an
</span><span style="color:#888"></span> <span style="color:#888">// instance of it to be injected via the constructor. The framework then
</span><span style="color:#888"></span> <span style="color:#888">// does what it needs to do in order to provide that dependency.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> QuotesController(QuoteService service)
{
<span style="color:#00d;font-weight:bold">_</span>service = service;
}
<span style="color:#888">// GET: api/Quotes
</span><span style="color:#888"></span><span style="color:#369"> [HttpGet]</span>
<span style="color:#888">// This method returns a collection of a new resource model instead of just the `Quote` entity directly.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<IEnumerable<SubmittedQuoteRequest>>> GetAll()
{
<span style="color:#888">// Instead of directly implementing the logic in this method, we call on
</span><span style="color:#888"></span> <span style="color:#888">// the service class and let it take care of the rest.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>service.GetAllQuotes();
}
<span style="color:#888">// POST: api/Quotes
</span><span style="color:#888"></span><span style="color:#369"> [HttpPost]</span>
<span style="color:#888">// This method receives as a paramater a `QuoteRequest` of just the `Quote` entity directly.
</span><span style="color:#888"></span> <span style="color:#888">// That way callers of this endpoint don't need to be exposed to the details of our data model implementation.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<SubmittedQuoteRequest>> Post(QuoteRequest request)
{
<span style="color:#888">// Instead of directly implementing the logic in this method, we call on
</span><span style="color:#888"></span> <span style="color:#888">// the service class and let it take care of the rest.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>service.CalculateQuote(request);
}
}
}
</code></pre></div><p>As you can see, we’ve once again opted to abstract away clients from the implementation details of our data model and used Resource Models for the API contract instead of the <code>Quote</code> entity directly. We have one for input data that’s called <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/ResourceModels/QuoteRequest.cs"><code>QuoteRequest</code></a> and another one for output: <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/master/ResourceModels/SubmittedQuoteRequest.cs"><code>SubmittedQuoteRequest</code></a>. Not very remarkable by themselves, but feel free to explore the source code in <a href="https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/master/ResourceModels">the GitHub repo</a>.</p>
<p>This controller has a dependency on <code>QuoteService</code>, which it uses to perform all of the necessary logic. This class is not defined yet so let’s do that next:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Collections.Generic</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Linq</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Threading.Tasks</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">Microsoft.EntityFrameworkCore</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Models</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.ResourceModels</span>;
<span style="color:#080;font-weight:bold">namespace</span> <span style="color:#b06;font-weight:bold">VehicleQuotes.Services</span>
{
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">QuoteService</span>
{
<span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">readonly</span> VehicleQuotesContext <span style="color:#00d;font-weight:bold">_</span>context;
<span style="color:#888">// This constructor defines a dependency on VehicleQuotesContext, similar to most of our controllers.
</span><span style="color:#888"></span> <span style="color:#888">// Via the built in dependency injection features, the framework makes sure to provide this parameter when
</span><span style="color:#888"></span> <span style="color:#888">// creating new instances of this class.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> QuoteService(VehicleQuotesContext context)
{
<span style="color:#00d;font-weight:bold">_</span>context = context;
}
<span style="color:#888">// This method takes all the records from the `quotes` table and constructs `SubmittedQuoteRequest`s with them.
</span><span style="color:#888"></span> <span style="color:#888">// Then returns that as a list.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<List<SubmittedQuoteRequest>> GetAllQuotes()
{
<span style="color:#888;font-weight:bold">var</span> quotesToReturn = <span style="color:#00d;font-weight:bold">_</span>context.Quotes.Select(q => <span style="color:#080;font-weight:bold">new</span> SubmittedQuoteRequest
{
ID = q.ID,
CreatedAt = q.CreatedAt,
OfferedQuote = q.OfferedQuote,
Message = q.Message,
Year = q.Year,
Make = q.Make,
Model = q.Model,
BodyType = q.BodyType.Name,
Size = q.Size.Name,
ItMoves = q.ItMoves,
HasAllWheels = q.HasAllWheels,
HasAlloyWheels = q.HasAlloyWheels,
HasAllTires = q.HasAllTires,
HasKey = q.HasKey,
HasTitle = q.HasTitle,
RequiresPickup = q.RequiresPickup,
HasEngine = q.HasEngine,
HasTransmission = q.HasTransmission,
HasCompleteInterior = q.HasCompleteInterior,
});
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> quotesToReturn.ToListAsync();
}
<span style="color:#888">// This method takes an incoming `QuoteRequest` and calculates a quote based on the vehicle described by it.
</span><span style="color:#888"></span> <span style="color:#888">// To calculate this quote, it looks for any overrides before trying to use the currently existing rules defined
</span><span style="color:#888"></span> <span style="color:#888">// in the `quote_rules` table. It also stores a record on the `quotes` table with all the incoming data and the
</span><span style="color:#888"></span> <span style="color:#888">// quote calculation result. It returns back the quote value as well as a message explaining the conditions of
</span><span style="color:#888"></span> <span style="color:#888">// the quote.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<SubmittedQuoteRequest> CalculateQuote(QuoteRequest request)
{
<span style="color:#888;font-weight:bold">var</span> response = <span style="color:#080;font-weight:bold">this</span>.CreateResponse(request);
<span style="color:#888;font-weight:bold">var</span> quoteToStore = <span style="color:#080;font-weight:bold">await</span> <span style="color:#080;font-weight:bold">this</span>.CreateQuote(request);
<span style="color:#888;font-weight:bold">var</span> requestedModelStyleYear = <span style="color:#080;font-weight:bold">await</span> <span style="color:#080;font-weight:bold">this</span>.FindModelStyleYear(request);
QuoteOverride quoteOverride = <span style="color:#080;font-weight:bold">null</span>;
<span style="color:#080;font-weight:bold">if</span> (requestedModelStyleYear != <span style="color:#080;font-weight:bold">null</span>)
{
quoteToStore.ModelStyleYear = requestedModelStyleYear;
quoteOverride = <span style="color:#080;font-weight:bold">await</span> <span style="color:#080;font-weight:bold">this</span>.FindQuoteOverride(requestedModelStyleYear);
<span style="color:#080;font-weight:bold">if</span> (quoteOverride != <span style="color:#080;font-weight:bold">null</span>)
{
response.OfferedQuote = quoteOverride.Price;
}
}
<span style="color:#080;font-weight:bold">if</span> (quoteOverride == <span style="color:#080;font-weight:bold">null</span>)
{
response.OfferedQuote = <span style="color:#080;font-weight:bold">await</span> <span style="color:#080;font-weight:bold">this</span>.CalculateOfferedQuote(request);
}
<span style="color:#080;font-weight:bold">if</span> (requestedModelStyleYear == <span style="color:#080;font-weight:bold">null</span>)
{
response.Message = <span style="color:#d20;background-color:#fff0f0">"Offer subject to change upon vehicle inspection."</span>;
}
quoteToStore.OfferedQuote = response.OfferedQuote;
quoteToStore.Message = response.Message;
<span style="color:#00d;font-weight:bold">_</span>context.Quotes.Add(quoteToStore);
<span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.SaveChangesAsync();
response.ID = quoteToStore.ID;
response.CreatedAt = quoteToStore.CreatedAt;
<span style="color:#080;font-weight:bold">return</span> response;
}
<span style="color:#888">// Creates a `SubmittedQuoteRequest`, initialized with default values, using the data from the incoming
</span><span style="color:#888"></span> <span style="color:#888">// `QuoteRequest`. `SubmittedQuoteRequest` is what gets returned in the response payload of the quote endpoints.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> SubmittedQuoteRequest CreateResponse(QuoteRequest request)
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> SubmittedQuoteRequest
{
OfferedQuote = <span style="color:#00d;font-weight:bold">0</span>,
Message = <span style="color:#d20;background-color:#fff0f0">"This is our final offer."</span>,
Year = request.Year,
Make = request.Make,
Model = request.Model,
BodyType = request.BodyType,
Size = request.Size,
ItMoves = request.ItMoves,
HasAllWheels = request.HasAllWheels,
HasAlloyWheels = request.HasAlloyWheels,
HasAllTires = request.HasAllTires,
HasKey = request.HasKey,
HasTitle = request.HasTitle,
RequiresPickup = request.RequiresPickup,
HasEngine = request.HasEngine,
HasTransmission = request.HasTransmission,
HasCompleteInterior = request.HasCompleteInterior,
};
}
<span style="color:#888">// Creates a `Quote` based on the data from the incoming `QuoteRequest`. This is the object that gets eventually
</span><span style="color:#888"></span> <span style="color:#888">// stored in the database.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">async</span> Task<Quote> CreateQuote(QuoteRequest request)
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">new</span> Quote
{
Year = request.Year,
Make = request.Make,
Model = request.Model,
BodyTypeID = (<span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.BodyTypes.SingleAsync(bt => bt.Name == request.BodyType)).ID,
SizeID = (<span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.Sizes.SingleAsync(s => s.Name == request.Size)).ID,
ItMoves = request.ItMoves,
HasAllWheels = request.HasAllWheels,
HasAlloyWheels = request.HasAlloyWheels,
HasAllTires = request.HasAllTires,
HasKey = request.HasKey,
HasTitle = request.HasTitle,
RequiresPickup = request.RequiresPickup,
HasEngine = request.HasEngine,
HasTransmission = request.HasTransmission,
HasCompleteInterior = request.HasCompleteInterior,
CreatedAt = DateTime.Now
};
}
<span style="color:#888">// Tries to find a registered vehicle that matches the one for which the quote is currently being requested.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">async</span> Task<ModelStyleYear> FindModelStyleYear(QuoteRequest request)
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.ModelStyleYears.FirstOrDefaultAsync(msy =>
msy.Year == request.Year &&
msy.ModelStyle.Model.Make.Name == request.Make &&
msy.ModelStyle.Model.Name == request.Model &&
msy.ModelStyle.BodyType.Name == request.BodyType &&
msy.ModelStyle.Size.Name == request.Size
);
}
<span style="color:#888">// Tries to find an override for the vehicle for which the quote is currently being requested.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">async</span> Task<QuoteOverride> FindQuoteOverride(ModelStyleYear modelStyleYear)
{
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.QuoteOverides
.FirstOrDefaultAsync(qo => qo.ModelStyleYear == modelStyleYear);
}
<span style="color:#888">// Uses the rules stored in the `quote_rules` table to calculate how much money to offer for the vehicle
</span><span style="color:#888"></span> <span style="color:#888">// described in the incoming `QuoteRequest`.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">private</span> <span style="color:#080;font-weight:bold">async</span> Task<<span style="color:#888;font-weight:bold">int</span>> CalculateOfferedQuote(QuoteRequest request)
{
<span style="color:#888;font-weight:bold">var</span> rules = <span style="color:#080;font-weight:bold">await</span> <span style="color:#00d;font-weight:bold">_</span>context.QuoteRules.ToListAsync();
<span style="color:#888">// Given a vehicle feature type, find a rule that applies to that feature type and has the value that
</span><span style="color:#888"></span> <span style="color:#888">// matches the condition of the incoming vehicle being quoted.
</span><span style="color:#888"></span> Func<<span style="color:#888;font-weight:bold">string</span>, QuoteRule> theMatchingRule = featureType =>
rules.FirstOrDefault(r =>
r.FeatureType == featureType &&
r.FeatureValue == request[featureType]
);
<span style="color:#888">// For each vehicle feature that we care about, sum up the the monetary values of all the rules that match
</span><span style="color:#888"></span> <span style="color:#888">// the given vehicle condition.
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">return</span> QuoteRule.FeatureTypes.All
.Select(theMatchingRule)
.Where(r => r != <span style="color:#080;font-weight:bold">null</span>)
.Sum(r => r.PriceModifier);
}
}
}
</code></pre></div><p>Finally, we need to tell the framework that this class is available for Dependency Injection. Similarly to how we did with our <code>VehicleQuotesContext</code>, we do so in the <code>Startup.cs</code> file’s <code>ConfigureServices</code> method. Just add this line at the top:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs">services.AddScoped<Services.QuoteService>();
</code></pre></div><blockquote>
<p>The core tenet of Inversion of Control is to depend on abstractions, not on implementations. So ideally, we would not have our controller directly call for a <code>QuoteService</code> instance. Instead, we would have it reference an abstraction, e.g. an interface like <code>IQuoteService</code>. The statement on <code>Startup.cs</code> would then look like this instead: <code>services.AddScoped<Services.IQuoteService, Services.QuoteService>();</code>.</p>
<p>This is important because it would allow us to unit test the component that depends on our service class (i.e. the controller in this case) by passing it a <a href="https://en.wikipedia.org/wiki/Mock_object">mock object</a> — one that also implements <code>IQuoteService</code> but does not really implement all the functionality of the actual <code>QuoteService</code> class. Since the controller only knows about the interface (that is, it “depends on an abstraction”), the actual object that we give it as a dependency doesn’t matter to it, as long as it implements that interface. This ability to inject mocks as dependencies is invaluable during testing. Testing is beyond the scope of this article though, so I’ll stick with the simpler approach with a static dependency on a concrete class. Know that this is not a good practice when it comes to actual production systems.</p>
</blockquote>
<p>And that’s all it takes. Once you add a few rules via <code>POST /api/QuoteRules</code>, you should be able to get some vehicles quoted with <code>POST /api/Quotes</code>. And also see what the system has stored via <code>GET /api/Quotes</code>.</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/a-quote.png" alt="A Quote"></p>
<p>And that’s all the functionality that we set out to build into our REST API! There are a few other neat things that I thought I’d include though.</p>
<h4 id="adding-seed-data-for-lookup-tables">Adding seed data for lookup tables</h4>
<p>Our vehicle size and body type data isn’t meant to really change much. In fact, we could even preload that data when our application starts. EF Core provides a data seeding feature that we can access via configurations on the <code>DbContext</code> itself. For our case, we could add this method to our <code>VehicleQuotesContext</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">protected</span> <span style="color:#080;font-weight:bold">override</span> <span style="color:#080;font-weight:bold">void</span> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Size>().HasData(
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">1</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Subcompact"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">2</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Compact"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">3</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Mid Size"</span> },
<span style="color:#080;font-weight:bold">new</span> Size { ID = <span style="color:#00d;font-weight:bold">5</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Full Size"</span> }
);
modelBuilder.Entity<BodyType>().HasData(
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">1</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Coupe"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">2</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Sedan"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">3</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Hatchback"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">4</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Wagon"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">5</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Convertible"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">6</span>, Name = <span style="color:#d20;background-color:#fff0f0">"SUV"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">7</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Truck"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">8</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Mini Van"</span> },
<span style="color:#080;font-weight:bold">new</span> BodyType { ID = <span style="color:#00d;font-weight:bold">9</span>, Name = <span style="color:#d20;background-color:#fff0f0">"Roadster"</span> }
);
}
</code></pre></div><p><a href="https://docs.microsoft.com/en-us/ef/core/modeling/#use-fluent-api-to-configure-a-model"><code>OnModelCreating</code></a> is a hook that we can define to run some code at the time the model is being created for the first time. Here, we’re using it to seed some data. In order to apply that, a migration needs to be created and executed. If you’ve added some data to the database, be sure to wipe it before running the migration so that we don’t run into unique constraint violations. Here are the migrations:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ dotnet ef migrations add AddSeedDataForSizesAndBodyTypes
$ dotnet ef database update
</code></pre></div><p>After that’s done, it no longer makes sense to allow creating, updating, deleting and fetching individual sizes and body types, so I would delete those endpoints from the respective controllers.</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/body-types-get-all-only.png" alt="Body Types, GET all only"></p>
<p><img src="/blog/2021/07/dotnet-5-web-api/sizes-get-all-only.png" alt="Sizes, GET all only"></p>
<blockquote>
<p>There are other options for data seeding in EF Core. Take a look: <a href="https://docs.microsoft.com/en-us/ef/core/modeling/data-seeding">Data Seeding</a>.</p>
</blockquote>
<h4 id="improving-the-swagger-ui-via-xml-comments">Improving the Swagger UI via XML comments</h4>
<p>Our current auto-generated Swagger UI is pretty awesome. Especially considering that we got it for free. It’s a little lacking when it comes to more documentation about specific endpoint summaries or expected responses. The good news is that there’s a way to leverage <a href="https://docs.microsoft.com/en-us/dotnet/csharp/codedoc">C# XML Comments</a> in order to improve the Swagger UI.</p>
<p>We can add support for that by configuring our project to produce, at build time, an XML file with the docs that we write. In order to do so, we need to update the <code>VehicleQuotes.csproj</code> like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff"><Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<!-- ... -->
<span style="color:#000;background-color:#dfd">+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</span><span style="color:#000;background-color:#dfd">+ <NoWarn>$(NoWarn);1591</NoWarn>
</span><span style="color:#000;background-color:#dfd"></span> </PropertyGroup>
<!-- ... -->
</Project>
</code></pre></div><p><code>GenerateDocumentationFile</code> is the flag that tells the .NET 5 build tools to generate the documentation file. The <code>NoWarn</code> element prevents our build output from getting cluttered with a lot of warnings saying that some classes and methods are not properly documented. We don’t want that because we just want to write enough documentation for the Swagger UI. And that includes only the controllers.</p>
<p>You can run <code>dotnet build</code> and look for the new file in <code>bin/Debug/net5.0/VehicleQuotes.xml</code>.</p>
<p>Then, we need to update <code>Startup.cs</code>. First we need to add the following <code>using</code> statements:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.IO</span>;
<span style="color:#080;font-weight:bold">using</span> <span style="color:#b06;font-weight:bold">System.Reflection</span>;
</code></pre></div><p>And add the following code to the <code>ConfigureServices</code> method on <code>Startup.cs</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "VehicleQuotes", Version = "v1" });
<span style="color:#000;background-color:#dfd">+ c.IncludeXmlComments(
</span><span style="color:#000;background-color:#dfd">+ Path.Combine(
</span><span style="color:#000;background-color:#dfd">+ AppContext.BaseDirectory,
</span><span style="color:#000;background-color:#dfd">+ $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"
</span><span style="color:#000;background-color:#dfd">+ )
</span><span style="color:#000;background-color:#dfd"></span> );
});
// ...
}
</code></pre></div><p>This makes it so the <code>SwaggerGen</code> service knows to look for the XML documentation file when building up the Open API specification file used for generating the Swagger UI.</p>
<p>Now that all of that is set up, we can actually write some XML comments and attributes that will enhance our Swagger UI. As an example, put this on top of <code>ModelsController</code>’s <code>Post</code> method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-cs" data-lang="cs"><span style="color:#888">/// <summary>
</span><span style="color:#888">/// Creates a new vehicle model for the given make.
</span><span style="color:#888">/// </summary>
</span><span style="color:#888">/// <param name="makeId">The ID of the vehicle make to add the model to.</param>
</span><span style="color:#888">/// <param name="model">The data to create the new model with.</param>
</span><span style="color:#888">/// <response code="201">When the request is valid.</response>
</span><span style="color:#888">/// <response code="404">When the specified vehicle make does not exist.</response>
</span><span style="color:#888">/// <response code="409">When there's already another model in the same make with the same name.</response>
</span><span style="color:#888"></span><span style="color:#369">[HttpPost]</span>
<span style="color:#369">[ProducesResponseType(StatusCodes.Status201Created)]</span>
<span style="color:#369">[ProducesResponseType(StatusCodes.Status404NotFound)]</span>
<span style="color:#369">[ProducesResponseType(StatusCodes.Status409Conflict)]</span>
<span style="color:#080;font-weight:bold">public</span> <span style="color:#080;font-weight:bold">async</span> Task<ActionResult<ModelSpecification>> Post([FromRoute] <span style="color:#888;font-weight:bold">int</span> makeId, ModelSpecification model)
{
<span style="color:#888">// ...
</span><span style="color:#888"></span>}
</code></pre></div><p>Then, the Swagger UI now looks like this for this endpoint:</p>
<p><img src="/blog/2021/07/dotnet-5-web-api/fully-documented-post-models.png" alt="Fully documented POST Models endpoint"></p>
<h4 id="configuring-the-app-via-settings-files-and-environment-variables">Configuring the app via settings files and environment variables</h4>
<p>Another aspect that’s important to web applications is having them be configurable via things like configuration files or environment variables. The framework already has provision for this, we just need to use it. I’m talking about the <code>appsettings</code> files.</p>
<p>We have two of them created for us by default: <code>appsettings.json</code> which is applied in all environments, and <code>appsettings.Development.json</code> that is applied only under development environments. The environment is given by the <code>ASPNETCORE_ENVIRONMENT</code> environment variable, and it can be set to either <code>Development</code>, <code>Staging</code>, or <code>Production</code> by default. That means that if we had, for example, an <code>appsettings.Staging.json</code> file, the settings defined within would be loaded if the <code>ASPNETCORE_ENVIRONMENT</code> environment variable were set to <code>Staging</code>. You get the idea.</p>
<blockquote>
<p>You can learn more about <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0">configuration</a> and <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-5.0">environments</a> in the official documentation.</p>
</blockquote>
<p>Anyway, let’s add a new setting on <code>appsettings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">{
// ...
<span style="color:#000;background-color:#dfd">+ "DefaultOffer": 77
</span><span style="color:#000;background-color:#dfd"></span>}
</code></pre></div><p>We’ll use this setting to give default offers when we’re not able to calculate appropriate quotes for vehicles. This can happen if we don’t have rules, or if the ones we have don’t match any of the incoming vehicle features or if for some other reason the final sum ends up in zero or negative number. We can use this setting in our <code>QuoteService</code> like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-diff" data-lang="diff">// ...
<span style="color:#000;background-color:#dfd">+using Microsoft.Extensions.Configuration;
</span><span style="color:#000;background-color:#dfd"></span>
namespace VehicleQuotes.Services
{
public class QuoteService
{
// ...
<span style="color:#000;background-color:#dfd">+ private readonly IConfiguration _configuration;
</span><span style="color:#000;background-color:#dfd"></span>
<span style="color:#000;background-color:#fdd">- public QuoteService(VehicleQuotesContext context)
</span><span style="color:#000;background-color:#fdd"></span><span style="color:#000;background-color:#dfd">+ public QuoteService(VehicleQuotesContext context, IConfiguration configuration)
</span><span style="color:#000;background-color:#dfd"></span> {
_context = context;
<span style="color:#000;background-color:#dfd">+ _configuration = configuration;
</span><span style="color:#000;background-color:#dfd"></span> }
// ...
public async Task<SubmittedQuoteRequest> CalculateQuote(QuoteRequest request)
{
// ...
<span style="color:#000;background-color:#dfd">+ if (response.OfferedQuote <= 0)
</span><span style="color:#000;background-color:#dfd">+ {
</span><span style="color:#000;background-color:#dfd">+ response.OfferedQuote = _configuration.GetValue<int>("DefaultOffer", 0);
</span><span style="color:#000;background-color:#dfd">+ }
</span><span style="color:#000;background-color:#dfd"></span>
quoteToStore.OfferedQuote = response.OfferedQuote;
// ...
}
// ...
}
}
</code></pre></div><p>Here, we’ve added a new parameter to the constructor to specify that <code>VehicleQuotesContext</code> has a dependency on <code>IConfiguration</code>. This prompts the framework to provide an instance of that when instantiating the class. We can use that instance to access the settings that we defined in the <code>appsettings.json</code> file via its <code>GetValue</code> method, like I demonstrated above.</p>
<p>The value of the settings in <code>appsettings.json</code> can be overridden by environment variables as well. On Linux, for example, we can run the app and set an environment value with a line like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ <span style="color:#369">DefaultOffer</span>=<span style="color:#00d;font-weight:bold">123</span> dotnet run
</code></pre></div><p>This will make the application use <code>123</code> instead of <code>77</code> when it comes to the <code>DefaultOffer</code> setting. This flexibility is great from a DevOps perspective. And we had to do minimal work in order to get that going.</p>
<h3 id="thats-all-for-now">That’s all for now</h3>
<p>And that’s it! In this article we’ve gone through many of the features offered in <a href="https://docs.microsoft.com/en-us/dotnet/core/dotnet-five">.NET 5</a>, <a href="https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-5.0">ASP.NET Core</a>, and <a href="https://docs.microsoft.com/en-us/ef/core/">Entity Framework Core</a> to support some of the most common use cases when it comes to developing Web API applications.</p>
<p>We’ve installed .NET 5 and created an ASP.NET Core Web API project with EF Core and a few bells and whistles, created controllers to support many different endpoints, played a little bit with routes and response codes, created and built upon a data model and updated a database via entities and migrations, implemented database constraints using unique indexes, implemented input validation using both built-in and custom validation attributes, implemented resource models as DTOs for defining the contract of some of our API endpoints, tapped into the built-in dependency injection capabilities, explored and improved the auto-generated Swagger UI, added seed data for our database, learned about configuration via settings files and environment variables.</p>
<p>.NET 5 is looking great.</p>
<h3 id="table-of-contents">Table of contents</h3>
<ul>
<li><a href="#what-were-building">What we’re building</a>
<ul>
<li><a href="#the-demo-application">The demo application</a></li>
<li><a href="#the-data-model">The data model</a></li>
</ul>
</li>
<li><a href="#the-development-environment">The development environment</a>
<ul>
<li><a href="#setting-up-the-postgresql-database-with-docker">Setting up the PostgreSQL database with Docker</a></li>
<li><a href="#installing-the-net-5-sdk">Installing the .NET 5 SDK</a></li>
</ul>
</li>
<li><a href="#setting-up-the-project">Setting up the project</a>
<ul>
<li><a href="#creating-our-aspnet-core-rest-api-project">Creating our ASP.NET Core REST API project</a></li>
<li><a href="#installing-packages-well-need">Installing packages we’ll need</a></li>
<li><a href="#connecting-to-the-database-and-performing-initial-app-configuration">Connecting to the database and performing initial app configuration</a></li>
</ul>
</li>
<li><a href="#building-the-application">Building the application</a>
<ul>
<li><a href="#creating-model-entities-migrations-and-updating-the-database">Creating model entities, migrations and updating the database</a></li>
<li><a href="#creating-controllers-for-cruding-our-tables">Creating controllers for CRUDing our tables</a></li>
<li><a href="#adding-unique-constraints-via-indexes">Adding unique constraints via indexes</a></li>
<li><a href="#responding-with-specific-http-error-codes-409-conflict">Responding with specific HTTP error codes (409 Conflict)</a></li>
<li><a href="#adding-a-more-complex-entity-to-the-model">Adding a more complex entity to the model</a></li>
<li><a href="#adding-composite-unique-indexes">Adding composite unique indexes</a></li>
<li><a href="#adding-controllers-with-custom-routes">Adding controllers with custom routes</a></li>
<li><a href="#using-resource-models-as-dtos-for-controllers">Using resource models as DTOs for controllers</a></li>
<li><a href="#validation-using-built-in-data-annotations">Validation using built-in Data Annotations</a></li>
<li><a href="#validation-using-custom-attributes">Validation using custom attributes</a></li>
<li><a href="#implementing-endpoints-for-quote-rules-and-overrides">Implementing endpoints for quote rules and overrides</a></li>
<li><a href="#implementing-the-quote-model">Implementing the quote model</a></li>
<li><a href="#using-dependency-injection">Using Dependency Injection</a></li>
<li><a href="#adding-seed-data-for-lookup-tables">Adding seed data for lookup tables</a></li>
<li><a href="#improving-the-swagger-ui-via-xml-comments">Improving the Swagger UI via XML comments</a></li>
<li><a href="#configuring-the-app-via-settings-files-and-environment-variables">Configuring the app via settings files and environment variables</a></li>
</ul>
</li>
<li><a href="#thats-all-for-now">That’s all for now</a></li>
</ul>
Decreasing your website load timehttps://www.endpointdev.com/blog/2020/01/decreasing-website-load-time/2020-01-07T00:00:00+00:00Juan Pablo Ventoso
<p><img src="/blog/2020/01/decreasing-website-load-time/mobile-desktop-browsing.jpg" alt="Decreasing our website load time" /> <a href="https://www.flickr.com/photos/johanl/6798184016/">Photo</a> by <a href="https://www.flickr.com/photos/johanl/">Johan Larsson</a>, used under <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a></p>
<p>We live in a competitive world, and the web is no different. Improving latency issues is crucial to any Search Engine Optimization (SEO) strategy, increasing the website’s ranking and organic traffic (visitors from search engines) as a result.</p>
<p>There are many factors that can lead to a faster response time, including optimization of your hosting plan, server proximity to your main traffic source, or utilization of a Content Distribution Network (CDN) if you are expecting visitors on an international level. Some of these solutions and many others can be implemented with only a couple hours of coding.</p>
<h3 id="inline-styles-and-scripts-for-the-topmost-content">Inline styles and scripts for the topmost content</h3>
<p>Nobody enjoys waiting for long load times. When opening a Google search link, being met with a blank page or a loading GIF for several seconds can seem agonizing. That’s why optimizing the initial rendering of your page is crucial.</p>
<p>The content that immediately appears to the user without the need to scroll down is referred to as “above-the-fold”. This is where your optimization efforts should be aimed. So here’s a plan to load and display as quickly as possible:</p>
<ul>
<li>
<p>First, differentiate the critical styles and scripts you need to render the topmost content, and separate them from the rest of our stylesheet and external script references.</p>
</li>
<li>
<p>Then, <a href="https://www.imperva.com/learn/performance/minification/">minify</a> the separated <a href="https://csscompressor.com/">styles</a> and <a href="https://jscompress.com/">scripts</a>, and insert them directly on our page template, right before the closing <code></head></code> tag.</p>
</li>
<li>
<p>Finally, take the stylesheet and scripts link references from the <code><head></code> tag (where it’s usually located) and move them to the end of the above-the-fold content.</p>
</li>
</ul>
<p>Now, the user won’t have to wait until all references are loaded before seeing content. <b>Tip</b>: Remember to use the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async">async</a> tag on scripts whenever possible.</p>
<p><strong>example.html:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">style</span>>{<span style="color:#a61717;background-color:#e3d2d2">above-the-fold</span> <span style="color:#a61717;background-color:#e3d2d2">minified</span> <span style="color:#a61717;background-color:#e3d2d2">inline</span> <span style="color:#a61717;background-color:#e3d2d2">styles</span> <span style="color:#a61717;background-color:#e3d2d2">goes</span> <span style="color:#a61717;background-color:#e3d2d2">here</span>}</<span style="color:#b06;font-weight:bold">style</span>>
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">type</span>=<span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>>{above-the-fold critical scripts goes here}</<span style="color:#b06;font-weight:bold">script</span>>
</<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">body</span>>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"above-the-fold-content"</span>></<span style="color:#b06;font-weight:bold">div</span>>
<<span style="color:#b06;font-weight:bold">link</span> <span style="color:#369">rel</span>=<span style="color:#d20;background-color:#fff0f0">"stylesheet"</span> <span style="color:#369">href</span>=<span style="color:#d20;background-color:#fff0f0">"{below-the-fold minified stylesheet reference goes here}"</span> />
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">async</span> <span style="color:#369">src</span>=<span style="color:#d20;background-color:#fff0f0">"{below-the-fold minified javascript reference goes here}"</span> />
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"below-the-fold-content"</span>></<span style="color:#b06;font-weight:bold">div</span>>
</<span style="color:#b06;font-weight:bold">body</span>>
</code></pre></div><h3 id="deferred-loading-of-ads">Deferred loading of ads</h3>
<p>If you’re monetizing your website through Google AdSense or another ad agency that uses scripts to load ads, consider loading ads after the content is fully rendered. This may have a small impact on your revenue, but will improve the user’s experience while optimizing the load speed.</p>
<p>Although there are several ways to achieve this, a technique I have successfully used on many websites is removing all of the script references to Google AdSense until your page is fully loaded. A short delay can be added in order to allow some browsing time before showing ads.</p>
<p>Remove script references, the comment, and extra spaces from your original ad code, to convert it from something like this…</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">async</span> <span style="color:#369">src</span>=<span style="color:#d20;background-color:#fff0f0">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"</span>></<span style="color:#b06;font-weight:bold">script</span>>
<span style="color:#888"><!-- Your ad name --></span>
<<span style="color:#b06;font-weight:bold">ins</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"adsbygoogle"</span>
<span style="color:#369">style</span>=<span style="color:#d20;background-color:#fff0f0">"display:inline-block;width:728px;height:90px"</span>
<span style="color:#369">data-ad-client</span>=<span style="color:#d20;background-color:#fff0f0">"ca-pub-XXXXXXXXXXXXXXXXX"</span>
<span style="color:#369">data-ad-slot</span>=<span style="color:#d20;background-color:#fff0f0">"XXXXXXXXX"</span>></<span style="color:#b06;font-weight:bold">ins</span>>
<<span style="color:#b06;font-weight:bold">script</span>>
(adsbygoogle = <span style="color:#038">window</span>.adsbygoogle || []).push({});
</<span style="color:#b06;font-weight:bold">script</span>>
</code></pre></div><p>… to something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">ins</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"adsbygoogle"</span> <span style="color:#369">style</span>=<span style="color:#d20;background-color:#fff0f0">"display:inline-block;width:728px;height:90px"</span> <span style="color:#369">data-ad-client</span>=<span style="color:#d20;background-color:#fff0f0">"ca-pub-XXXXXXXXXXXXXXXXX"</span> <span style="color:#369">data-ad-slot</span>=<span style="color:#d20;background-color:#fff0f0">"XXXXXXXXX"</span>></<span style="color:#b06;font-weight:bold">ins</span>>
</code></pre></div><p>A lot shorter, isn’t it? This will create an empty slot in which the ad will be displayed after the page is fully rendered. To accomplish that, a new script like the one below must be added (assuming jQuery is present on the website):</p>
<p><strong>async-ads.js:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#888">// Create a script reference
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">function</span> addScript(src, <span style="color:#080;font-weight:bold">async</span>, callback) {
<span style="color:#080;font-weight:bold">var</span> js = <span style="color:#038">document</span>.createElement(<span style="color:#d20;background-color:#fff0f0">"script"</span>);
js.type = <span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>;
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">async</span>)
js.<span style="color:#080;font-weight:bold">async</span> = <span style="color:#080;font-weight:bold">true</span>;
<span style="color:#080;font-weight:bold">if</span> (callback)
js.onload = callback;
js.src = src;
<span style="color:#038">document</span>.body.appendChild(js);
}
<span style="color:#888">// Called when document is ready
</span><span style="color:#888"></span>$(<span style="color:#038">document</span>).ready(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#888">// Wait for one second to ensure the user started browsing
</span><span style="color:#888"></span> setTimeout(<span style="color:#080;font-weight:bold">function</span>() {
(adsbygoogle = <span style="color:#038">window</span>.adsbygoogle || []);
$(<span style="color:#d20;background-color:#fff0f0">"ins.adsbygoogle"</span>).each(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>"</span>).insertAfter($(<span style="color:#080;font-weight:bold">this</span>));
});
addScript(<span style="color:#d20;background-color:#fff0f0">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"</span>, <span style="color:#080;font-weight:bold">true</span>);
}, <span style="color:#00d;font-weight:bold">1000</span>);
});
</code></pre></div><p>This code will wait for one second once the document is ready, and then leave instructions for Google to push a new ad for each slot. Finally, the AdSense external script will be loaded so that Google will read the instructions and start filling all the slots with ads.</p>
<p><b>Tip</b>: Enabling balancing from your AdSense dashboard may improve the average load speed as well as the user’s experience since ads will not be shown when the expected revenue is deprecable. And if you’re still on the fence about showing fewer ads, <a href="https://fatstacksblog.com/adsense-ad-balance-experiment/">try out an experiment</a> like I did. A balance of 50% worked well in my case, but the right balance will depend on your niche and website characteristics.</p>
<h3 id="lazy-load-for-images">Lazy load for images</h3>
<p>Because the user will most likely spend the majority of the visit reading above-the-fold content (and may even leave before scrolling at all), loading all images from content below-the-fold at first is impractical. Implementing a custom lazy-loading script (also referred to as deferred-loading or loading-on-scroll) for images can be an easy process. Even though changes to the backend would be likely, the concept of this approach is simple:</p>
<ul>
<li>
<p>Replacing the <code>src</code> attributes from all images that will have lazy loading with a custom attribute such as <code>data-src</code> (this part will probably require backend changes) and set a custom class for them, like <code>lazy</code>.</p>
</li>
<li>
<p>Creating a script that will copy the <code>data-src</code> content into the <code>src</code> attribute as we scroll through the page.</p>
</li>
</ul>
<p><strong>lazy-load.js:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript">;(<span style="color:#080;font-weight:bold">function</span>($) {
$.fn.lazy = <span style="color:#080;font-weight:bold">function</span>(threshold, callback) {
<span style="color:#080;font-weight:bold">var</span> $w = $(<span style="color:#038">window</span>),
th = threshold || <span style="color:#00d;font-weight:bold">0</span>,
attrib = <span style="color:#d20;background-color:#fff0f0">"data-src"</span>,
images = <span style="color:#080;font-weight:bold">this</span>,
loaded;
<span style="color:#080;font-weight:bold">this</span>.one(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>, <span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">var</span> source = <span style="color:#080;font-weight:bold">this</span>.getAttribute(attrib);
source = source || <span style="color:#080;font-weight:bold">this</span>.getAttribute(<span style="color:#d20;background-color:#fff0f0">"data-src"</span>);
<span style="color:#080;font-weight:bold">if</span> (source) {
<span style="color:#080;font-weight:bold">this</span>.setAttribute(<span style="color:#d20;background-color:#fff0f0">"src"</span>, source);
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">typeof</span> callback === <span style="color:#d20;background-color:#fff0f0">"function"</span>) callback.call(<span style="color:#080;font-weight:bold">this</span>);
}
});
<span style="color:#080;font-weight:bold">function</span> lazy() {
<span style="color:#080;font-weight:bold">var</span> inview = images.filter(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">var</span> $e = $(<span style="color:#080;font-weight:bold">this</span>);
<span style="color:#080;font-weight:bold">if</span> ($e.is(<span style="color:#d20;background-color:#fff0f0">":hidden"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">var</span> wt = $w.scrollTop(),
wb = wt + $w.height(),
et = $e.offset().top,
eb = et + $e.height();
<span style="color:#080;font-weight:bold">return</span> eb >= wt - th && et <= wb + th;
});
loaded = inview.trigger(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>);
images = images.not(loaded);
}
$w.scroll(lazy);
$w.resize(lazy);
lazy();
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">this</span>;
};
})(<span style="color:#038">window</span>.jQuery);
$(<span style="color:#038">document</span>).ready(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">'.lazy'</span>).each(<span style="color:#080;font-weight:bold">function</span> () {
$(<span style="color:#080;font-weight:bold">this</span>).lazy(<span style="color:#00d;font-weight:bold">0</span>, <span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#080;font-weight:bold">this</span>).load(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">this</span>.style.opacity = <span style="color:#00d;font-weight:bold">1</span>;
});
});
});
<span style="color:#888">// Set the correct attribute when printing
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">var</span> beforePrint = <span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">"img.lazy"</span>).each(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#080;font-weight:bold">this</span>).trigger(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>);
<span style="color:#080;font-weight:bold">this</span>.style.opacity = <span style="color:#00d;font-weight:bold">1</span>;
});
};
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#038">window</span>.matchMedia) {
<span style="color:#080;font-weight:bold">var</span> mediaQueryList = <span style="color:#038">window</span>.matchMedia(<span style="color:#d20;background-color:#fff0f0">'print'</span>);
mediaQueryList.addListener(<span style="color:#080;font-weight:bold">function</span>(mql) {
<span style="color:#080;font-weight:bold">if</span> (mql.matches)
beforePrint();
});
}
<span style="color:#038">window</span>.onbeforeprint = beforePrint;
</code></pre></div><p>This script will search for all <code><img></code> tags with class <code>lazy</code>, and change the <code>data-src</code> attribute to the <code>src</code> attribute once the image becomes visible due to scrolling. It also includes some additional logic to set the <code>src</code> attribute before printing the page.</p>
<h3 id="server-side-caching">Server-side caching</h3>
<p>Instead of performing all the backend rendering calculations every time, server-side caching allows you to output the same content to the clients over a period of time from a temporary copy of the response. This not only results in a decreased response time but also saves some resources on the server.</p>
<p>There are several ways to enable server-side caching, depending on factors such as the backend language and hosting platform (e.g. Windows/IIS vs. Linux/Apache), among other things. For this example, we will use ASP.NET (C#) since I’m mostly a Windows user.</p>
<p>The best and most efficient way to do this is by adding a declaration in the top of our ASP.NET page:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><%<span style="color:#a61717;background-color:#e3d2d2">@</span> OutputCache Duration=<span style="color:#d20;background-color:#fff0f0">"10"</span> VaryByParam=<span style="color:#d20;background-color:#fff0f0">"id;date"</span> %>
</code></pre></div><p>This declaration is telling the compiler that we want to cache the output from the server for 10 minutes, and we will save different versions based on the <code>id</code> and <code>date</code> URL parameters. So pages like:</p>
<ul>
<li>https://www.your-url.com/cached-page/?id=1&date=2020-01-01</li>
<li>https://www.your-url.com/cached-page/?id=2&date=2020-01-01</li>
<li>https://www.your-url.com/cached-page/?id=2&date=2020-02-01</li>
</ul>
<p>will be saved and then served from different cache copies. If we only set the <code>id</code> parameter as a source for caching, pages with different dates will be served from the same cache source (this can be useful as the <code>date</code> parameter is only evaluated on frontend scripts and ignored in the backend).</p>
<p>There are other configurations in ASP.NET to set our output cache policy. The output can be set to be based on the browser, the request headers, or even custom strings. <a href="https://www.c-sharpcorner.com/UploadFile/chinnasrihari/Asp-Net-mvc-framework-server-side-html-caching-techniques/">This page</a> has more useful information on this subject.</p>
<h3 id="gzip-compression">GZip compression</h3>
<p>GZip compression—when the client supports it—allows compressing the response before sending it over the network. In this way, more than 70% of the bandwidth can be saved when loading the website. Enabling GZip compression for dynamic and static content on a Windows Server with IIS is simple: Just go to the “Compression” section on the IIS Manager and check the options “Enable dynamic/static content compression”.</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/enabling-compression-iis.jpg" alt="Enabling compression in IIS"></p>
<p>However, if you are running an ASP.NET MVC/WebForms website, this won’t be enough. For all backend responses to be compressed before sending them to the client, some custom code will also need to be added to the <code>global.asax</code> file in the website root:</p>
<p><strong>global.asax:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><%<span style="color:#a61717;background-color:#e3d2d2">@</span> Application Language=<span style="color:#d20;background-color:#fff0f0">"C#"</span> %>
<script runat=<span style="color:#d20;background-color:#fff0f0">"server"</span>>
<span style="color:#080;font-weight:bold">void</span> Application_PreRequestHandlerExecute(<span style="color:#888;font-weight:bold">object</span> sender, EventArgs e)
{
HttpApplication app = sender <span style="color:#080;font-weight:bold">as</span> HttpApplication;
<span style="color:#888;font-weight:bold">string</span> acceptEncoding = app.Request.Headers[<span style="color:#d20;background-color:#fff0f0">"Accept-Encoding"</span>];
System.IO.Stream prevUncompressedStream = app.Response.Filter;
<span style="color:#080;font-weight:bold">if</span> (app.Context.CurrentHandler == <span style="color:#080;font-weight:bold">null</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (!(app.Context.CurrentHandler <span style="color:#080;font-weight:bold">is</span> System.Web.UI.Page ||
app.Context.CurrentHandler.GetType().Name == <span style="color:#d20;background-color:#fff0f0">"SyncSessionlessHandler"</span>) ||
app.Request[<span style="color:#d20;background-color:#fff0f0">"HTTP_X_MICROSOFTAJAX"</span>] != <span style="color:#080;font-weight:bold">null</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (acceptEncoding == <span style="color:#080;font-weight:bold">null</span> || acceptEncoding.Length == <span style="color:#00d;font-weight:bold">0</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.ServerVariables[<span style="color:#d20;background-color:#fff0f0">"SCRIPT_NAME"</span>].ToLower().Contains(<span style="color:#d20;background-color:#fff0f0">".axd"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.ServerVariables[<span style="color:#d20;background-color:#fff0f0">"SCRIPT_NAME"</span>].ToLower().Contains(<span style="color:#d20;background-color:#fff0f0">".js"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.QueryString.ToString().Contains(<span style="color:#d20;background-color:#fff0f0">"_TSM_HiddenField_"</span>)) <span style="color:#080;font-weight:bold">return</span>;
acceptEncoding = acceptEncoding.ToLower();
<span style="color:#080;font-weight:bold">if</span> (acceptEncoding.Contains(<span style="color:#d20;background-color:#fff0f0">"deflate"</span>) || acceptEncoding == <span style="color:#d20;background-color:#fff0f0">"*"</span>)
{
app.Response.Filter = <span style="color:#080;font-weight:bold">new</span> System.IO.Compression.DeflateStream(prevUncompressedStream,
System.IO.Compression.CompressionMode.Compress);
app.Response.AppendHeader(<span style="color:#d20;background-color:#fff0f0">"Content-Encoding"</span>, <span style="color:#d20;background-color:#fff0f0">"deflate"</span>);
}
<span style="color:#080;font-weight:bold">else</span> <span style="color:#080;font-weight:bold">if</span> (acceptEncoding.Contains(<span style="color:#d20;background-color:#fff0f0">"gzip"</span>))
{
app.Response.Filter = <span style="color:#080;font-weight:bold">new</span> System.IO.Compression.GZipStream(prevUncompressedStream,
System.IO.Compression.CompressionMode.Compress);
app.Response.AppendHeader(<span style="color:#d20;background-color:#fff0f0">"Content-Encoding"</span>, <span style="color:#d20;background-color:#fff0f0">"gzip"</span>);
}
}
</script>
</code></pre></div><p>To make sure our code is working properly, an external tool like <a href="https://www.giftofspeed.com/gzip-test/">this</a> will inform you if GZip is enabled or not.</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/gzip-compression-enabled.jpg" alt="It works!"></p>
<h3 id="summary">Summary</h3>
<p>While there are many ways of decreasing the load time of a website, most are common and expensive. However, with a few minor tweaks, we can offer a better user experience in addition to improve our position in the search engine results. Every bit of optimization counts towards the goal with SEO. Load time is a very important factor (to both the developer and the user), especially on mobile platforms where users expect to get what they want instantly.</p>
<p>The image below is a Google Analytics report from one of my websites where, over several months, I implemented most of these formulas. A month ago, I made the latest change of deferring ad loading, which had an observable impact on the average loading speed of the page:</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/analytics-average-page-load.jpg" alt="Report from Google Analytics"></p>
<p>Do you have any other page load optimization techniques? <b>Leave a comment below!</b></p>
What’s the deal with ASP.NET Core Razor Pages?https://www.endpointdev.com/blog/2018/11/whats-the-deal-with-asp-net-core-razor-pages/2018-11-20T00:00:00+00:00Kevin Campusano
<p><img src="whats-the-deal-with-asp-net-core-razor-pages/banner.png" alt="Banner Image"></p>
<p>During the last couple of years I’ve been doing lots of web development with technologies like <a href="https://en.wikipedia.org/wiki/JavaScript">JavaScript</a> and <a href="http://php.net/">PHP</a> and <a href="https://framework.zend.com/">Zend Framework</a>, with a strong focus on the front end. Before that, however, the vast majority of the work I did as a web developer was with the <a href="https://www.microsoft.com/net">.NET Framework</a>.</p>
<p>I love .NET. Particularly the <a href="https://docs.microsoft.com/en-us/dotnet/csharp/">C# language</a>. However, the greatest thing about .NET is the vibrant ecosystem full of tools (the <a href="https://visualstudio.microsoft.com/">Visual Studio IDE</a>, for example, is outstanding), libraries and frameworks that make one’s life easier and more productive. It has, however, a crucial weakness that prevents it from reaching even greater heights: it’s locked to Windows systems. So, whenever the need comes to develop something outside of a Windows environment, you’re out of luck.</p>
<p>So, naturally, when Microsoft announced their initiative to make the .NET Framework open source and bring it to other platforms a few years ago, I was really excited. Fast forward to today and we have <a href="https://dotnet.github.io/">.NET Core</a> as the product of great effort from both Microsoft and the community.</p>
<p>I’ve definitely kept it on my radar since its inception back in 2016, but haven’t had the chance to take a really deep dive and see what the technology is about and get a feel for the ecosystem surrounding it.</p>
<p>Well, that time has come and just last week I decided to go in and see what’s up and how everything works. Suffice it to say, I am impressed. The promise of being able to write .NET code everywhere has been pretty much fulfilled.</p>
<p>In terms of features, .NET Core is still missing some stuff but the feature set is mature enough that it can handle most use cases. Me being first and foremost a web developer though, what has me most excited is <a href="https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-2.1">ASP.NET Core</a>, .NET Core’s own web development framework inspired by <a href="https://www.asp.net/">classic ASP.NET</a>.</p>
<p>ASP.NET Core is not just a port of ASP.NET, it is a complete rewrite with a new architecture, new features and ways to develop apps.</p>
<p>At the core of ASP.NET Core (no pun intended) there’s the notion that an HTTP request gets processed by a pipeline composed of several components named middleware, which also produce a response. When the server receives a request, each of the middleware components in the pipeline take their turn in processing the request, are responsible for calling the next middleware in the pipeline and, finally, generating a response to send back to the client.</p>
<p>ASP.NET Core is not the only modern web framework with this pipeline based request processing design. <a href="https://docs.zendframework.com/zend-expressive/">Zend Expressive</a>, for example, also uses this architecture.</p>
<p>Overall, I think this approach is a good solution to the problem of request processing which results in a good <a href="https://en.wikipedia.org/wiki/Mental_model">mental model</a> for developers to have about how the application is working under the hood.</p>
<p>All of this is more concerned with application startup and initial configuration though. When it comes to actually writing business logic, ASP.NET Core offers two alternatives for web apps with GUIs: <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-2.1">MVC</a> and, most recently, <a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-2.1&tabs=visual-studio">Razor Pages</a>.</p>
<p>From the point of view of the developer’s experience, MVC is very similar to classic ASP.NET MVC: there are Controllers which contain Action Methods which return Views which are processed by a templating engine (i.e. <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-2.1">Razor</a>) to generate a GUI that a user can see and interact with. Razor Pages though, is a new approach that is exclusive to ASP.NET Core and makes things slightly different. The developers at Microsoft say that this new approach is even preferred to MVC for building web UIs. That is, non-<a href="https://en.wikipedia.org/wiki/Web_API">API</a> web applications.</p>
<p>With Razor Pages, Controllers, Actions and Views are gone and in their place we’ve got Pages and so-called Page Models. So, while in MVC we have the router match a URL to a Controller and Action combo that handles the request and returns a processed View to the client; in Razor Pages we have the router match a URL to a Page and an optional Page Model file (via their location in the project directory) that handle the request and finally present some GUI to the client. Another way to look at it is that, while MVC focuses on Controllers and Actions to handle a request, Razor Pages focuses on actual files.</p>
<p>To better illustrate their differences, here’s what the directory structure of each looks like:</p>
<p><img src="whats-the-deal-with-asp-net-core-razor-pages/files-comparison.png" alt="MVC vs Razor pages files comparison"></p>
<p>As you can see, the main difference is that the Controllers, Models and Views folders are gone and instead we have a Pages folder. The Pages folder contains all of the .cshtml files (which are the Views or templates) together with accompanying .cshtml.cs files (which are the presentation logic behind the views, or Page Models). These Page Model files contain the logic to respond to requests as well as any properties containing values used for presentation in the template. These properties are a substitute for having separate View Model <a href="https://www.martinfowler.com/eaaCatalog/dataTransferObject.html">DTO</a> objects that the Actions pass to the Views.</p>
<p>Looking at this directory structure, it’s very easy to see that this model, on the surface at least, looks an awful lot like a Page/Code Behind model that hasn’t been used since the first iteration of ASP.NET: <a href="https://docs.microsoft.com/en-us/aspnet/web-forms/what-is-web-forms">Web Forms</a>. This, together with the fact that MVC’s clean cut separation of Controllers and Views seems to have been merged into some <a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">SRP</a>-violating Page Model file, has raised some red flags in the eyes of some people in the community.</p>
<p>Perhaps unsurprisingly, Razor Pages has sparked quite the controversy. There’s a big discussion over on the <a href="https://github.com/aspnet/Docs/issues/6146">project’s GitHub page</a>, as I’m sure there is elsewhere in places like <a href="https://stackoverflow.com/questions/46777404/why-is-razor-pages-the-recommended-approach-to-create-a-web-ui-in-asp-net-core-2">StackOverflow</a>.</p>
<p>And, I gotta be honest, I myself, being very familiar with Web Forms and its pitfalls, had a similar initial gut reaction as well. And I think everything is also compounded by the fact that now, seemingly out of nowhere, the ASP.NET Core team are touting this new, inferior (to the eyes of some) method as the preferred method for building new applications moving forward. So, people fear that their preferred style will stop being supported and go away for good.</p>
<p>So, what is the truth? Well, I don’t think it’s as bad as some people are making it out to be. Of course, the technology is fairly new and still needs to be battle tested in large scale real world projects to make sure it’s got what it takes. But as of now, I don’t think the design is fundamentally flawed. In fact, I think it offers a competent way to develop web apps, and I can definitely see why the ASP.NET Core team sees this as the preferred method over MVC.</p>
<p>I’m going to offer some arguments as to why I see Razor Pages as a perfectly fine technology choice. To do that, I’ll address some of the critiques that I’ve seen have been levied against it.</p>
<h3 id="1-its-a-worse-design-than-mvc">1. It’s a worse design than MVC</h3>
<p>Some people are saying that MVC is just better on the grounds that it has better <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">Separation of Concerns</a>. This may look like fact at first because of how the files are structured. Like we saw before, in MVC, presentation logic is distributed among three types of files: Controllers (which contain Action Methods), Views and, generally View Models. In Razor Pages we only have a template and an accompanying file which merges Controller, Action and View Model all together. However, we have to ask ourselves: in MVC, are Controllers, Actions and View Models truly separated? And should they even be?</p>
<p>In the context of MVC, View Models are nothing more than DTOs that serve as containers for data that the Actions in the Controller send to the View for rendering. As such, these are most of the time tightly and statically coupled with the View that uses them and the Action that generates them. Rarely you see a View Model being reused across multiple Actions and Views, or injected as an abstract dependency to the Controller.</p>
<p>So, this means that, with a design like the one Razor Pages uses, we’re not actually losing much in the way of Separation of Concerns. Actually, we gain something in the convenience of having all related presentation logic consolidated and close together instead of dispersed across the directory tree.</p>
<p>Finally, I think one frame of mind that helps further see the value of what Razor Pages offers is thinking of the Page Model as a View Model. And I mean View Model in the sense of <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel">MVVM</a> as opposed to its MVC counterpart. As we’ve already discussed, in MVC, a View Model is nothing more than a DTO that serves to carry data from the Controller to the View. In MVVM, a View Model is much more, as it serves not only as a data store but also as a mechanism to respond to user interactions and execution of presentation logic. This is the pattern used by many modern front end development frameworks like <a href="https://reactjs.org/">React</a>, <a href="https://angularjs.org/">Angular</a> and <a href="https://vuejs.org/">Vue.js</a>. The only difference is that, while the MVVM JavaScript frameworks bridge the gap between the <a href="https://www.w3schools.com/js/js_htmldom.asp">DOM</a> and our app’s code; Razor Pages is bridging the gap between an HTTP client and a server. As a consequence, the vocabulary of the messages is completely different. In the front end, we have the View Model responding to click, and hover and mouseEnter events that come from the DOM. In Razor Pages, we have our “View Model” (i.e. the Page Model) responding to events that come from the client via HTTP; so we get things like GET or POST or PUT, with the associated request payloads.</p>
<h3 id="2-its-just-web-forms-all-over-again">2. It’s just Web Forms all over again</h3>
<p>This one is simply not true as both technologies are very different. It is true that, from a file organization point of view, you can definitely see some similarities. After all, both in Razor Pages and Web Forms we have the focus on pages with some code behind them. However, the way that they go about implementing actual websites is completely different.</p>
<p>Just to be clear, I don’t subscribe to the idea that Web Forms is a bad technology or that it was badly designed. For its time, Web Forms was a huge step forward in terms of rapid application development. Also, its model tried to abstract away all the nuance of HTTP and offer a development experience that was very similar to what was seen before in Windows Forms. Now, most modern web development frameworks embrace HTTP’s idiosyncrasies rather than trying to take them out of the way of developers. However, at the time it was an interesting proposition that helped to bridge the gap that developers had to cross in order to move from building desktop apps on Windows to building web apps on a Windows based server.</p>
<p>Anyway, Razor Pages is very different from ASP.NET Web Forms. First of all, in Razor Pages, there’s no abstraction of HTTP whatsoever. Actually, what you see in those Page Model files is not an HTML page’s “Code Behind“, what you see are HTTP-<a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">Verb</a>-specific Action Methods of sorts that the framework calls for handling requests. You’re going to see methods like OnGet and OnPost, instead of Button3_OnClick or DropDownList1_OnSelect. Much like MVC, you also have access to the complete Request, Response and HttpContext objects that help out in processing requests. Also, the main pain points in WebFroms: Postbacks and the ViewState, are nowhere to be found. Instead, everything is stateless and you’re 100% in control of the messaging that’s happening between server and client.</p>
<p>Here’s what an empty Page Model file looks like:</p>
<p><img src="whats-the-deal-with-asp-net-core-razor-pages/pagemodel.png" alt="Empty Razor Pages Page Model class"></p>
<p>In reality, these Page Model files look quite a bit like traditional MVC Controllers in that they contain, essentially, Action Methods. The difference is that, in MVC, we had many Action Methods in one Controller where each Action Method corresponded to a specific route. Through routing, each Action Method was executed depending on the incoming request URL. Then the Action Method was responsible of handling the request depending on the HTTP Verb (i.e. GET, POST, etc) and other incoming data. Finally it would produce a useful response.</p>
<p>Meanwhile, in Razor Pages we have these “Action Methods” of sorts that are geared towards handling the various HTTP Verbs for requests that come to the front facing page that they are associated to. This means that a given Page Model and Page combo will always only serve a particular, specific route (i.e. URL). Where a traditional MVC Controller would potentially server multiple routes, depending on how many Action Methods were defined within it.</p>
<p>So, while it is true that, with Razor Pages, we “went back” to having pairs of files representing one web app functional unit, like we did in ASP.NET Web Forms; the way they go about it and the developer experience that they offer is completely different. Also, Razor Pages is not that different than MVC after all. All the building blocks are still there, they are just slightly rearranged.</p>
<h3 id="3-its-going-to-replace-mvc-completely">3. It’s going to replace MVC completely</h3>
<p>I understand the fear that, when the new thing comes out, the old thing is going to be left to the wayside. This is the same feeling that many had (me included!) when ASP.NET MVC for the <a href="https://www.urbandictionary.com/define.php?term=OG">OG</a> .NET Framework first came out way back in 2009. Web Forms was doomed. Well, guess what? Web Forms is still around. While it’s true that the last major update happened years ago, the technology is still a first class citizen in the .NET ecosystem and <a href="https://docs.microsoft.com/en-us/aspnet/web-forms/what-is-web-forms#advantages-of-a-web-forms-based-web-application">perfectly usable</a>. And I attribute the lack of recent big updates with new features to the fact that it is a mature framework that is “pretty complete”, rather than Microsoft pulling the plug. After all, it’s still supported in the newest version of Visual Studio and the .NET Framework and <a href="https://www.youtube.com/watch?v=KFeuCplwhaQ">new features and improvements do get developed</a>, albeit small.</p>
<p>Here’s what Visual Studio shows when asked to create a new web application project:</p>
<p><img src="whats-the-deal-with-asp-net-core-razor-pages/webformslives.png" alt="Create New ASP.NET Web Application dialog"></p>
<p>Yup, still alive and kicking.</p>
<p>So yeah, I feel like this is pretty natural human behavior but it’s ultimately baseless, given the history of this ecosystem. Much like Web Forms is still alive after years and years, I think MVC in ASP.NET Core is going to be just fine.</p>
<p>And that concludes my long winded opinion piece about the whole MVC vs Razor Pages controversy and my recent discovery of how cool the .NET Core framework is. As far as I see it, the technology is ready for prime time and I’m looking forward to using it in my projects.</p>
<p>(Reposted from <a href="https://superlativelabs.com/2018/08/14/whats-the-deal-with-asp-net-core-razor-pages/">Kevin’s blog</a>.)</p>