ASP.NET Core updates in .NET Core 3.0 Preview 2 - Printable Version +- Sick Gaming (https://www.sickgaming.net) +-- Forum: Programming (https://www.sickgaming.net/forum-76.html) +--- Forum: C#, Visual Basic, & .Net Frameworks (https://www.sickgaming.net/forum-79.html) +--- Thread: ASP.NET Core updates in .NET Core 3.0 Preview 2 (/thread-89060.html) |
ASP.NET Core updates in .NET Core 3.0 Preview 2 - xSicKxBot - 03-06-2019 ASP.NET Core updates in .NET Core 3.0 Preview 2 <div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2.png" width="1376" height="968" title="" alt="" /></div><div><p><a href="https://blogs.msdn.microsoft.com/dotnet/2019/01/29/announcing-net-core-3-preview-2/">.NET Core 3.0 Preview 2 is now available</a> and it includes a bunch of new updates to ASP.NET Core.</p> <p>Here’s the list of what’s new in this preview:</p> <ul> <li>Razor Components</li> <li>SignalR client-to-server streaming</li> <li>Pipes on HttpContext</li> <li>Generic host in templates</li> <li>Endpoint routing updates</li> </ul> <h2 id="get-started">Get started</h2> <p>To get started with ASP.NET Core in .NET Core 3.0 Preview 2 <a href="https://dotnet.microsoft.com/download/dotnet-core/3.0">install the .NET Core 3.0 Preview 2 SDK</a></p> <p>If you’re on Windows using Visual Studio, you’ll also want to <a href="https://visualstudio.com/preview">install the latest preview of Visual Studio 2019</a>.</p> <h2 id="upgrade-an-existing-project">Upgrade an existing project</h2> <p>To upgrade an existing an ASP.NET Core app to .NET Core 3.0 Preview 2, follow the <a href="https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30">migrations steps in the ASP.NET Core docs</a>.</p> <h3 id="add-package-for-json-net">Add package for Json.NET</h3> <p>As part of the work to <a href="https://blogs.msdn.microsoft.com/webdev/2018/10/29/a-first-look-at-changes-coming-in-asp-net-core-3-0/">tidy up the ASP.NET Core shared framework</a>, Json.NET is being removed from the shared framework and now needs to be added as a package. </p> <p>To add back Json.NET support to an ASP.NET Core 3.0 project:</p> <h3 id="runtime-compilation-removed">Runtime compilation removed</h3> <p>As a consequence of <a href="https://github.com/aspnet/Announcements/issues/325">cleaning up the ASP.NET Core shared framework to not depend on Roslyn</a>, support for runtime compilation of pages and views has also been removed in this preview release. Instead compilation of pages and views is performed at build time. In a future preview update we will provide a NuGet packages for optionally enabling runtime compilation support in an app.</p> <h3 id="other-breaking-changes-and-announcements">Other breaking changes and announcements</h3> <p>For a full list of other breaking changes and announcements for this release please see the ASP.NET Core <a href="https://github.com/aspnet/announcements/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0.0">Announcements</a> repo.</p> <h2 id="build-modern-web-ui-with-razor-components">Build modern web UI with Razor Components</h2> <p>Razor Components are a new way to build interactive client-side web UI with ASP.NET Core. This release of .NET Core 3.0 Preview 2 adds support for Razor Components to ASP.NET Core and for hosting Razor Components on the server. For those of you who have been following along with the experimental <a href="https://blazor.net">Blazor</a> project, Razor Components represent the integration of the Blazor component model into ASP.NET Core along with the server-side Blazor hosting model. ASP.NET Core Razor Components is a new capability in ASP.NET Core to host Razor Components on the server over a real-time connection.</p> <h3 id="working-with-razor-components">Working with Razor Components</h3> <p>Razor Components are self-contained chunks of user interface (UI), such as a page, dialog, or form. Razor Components are normal .NET classes that define UI rendering logic and client-side event handlers, so you can write rich interactive web apps without having to write any JavaScript. Razor components are typically authored using Razor syntax, a natural blend of HTML and C#. Razor Components are similar to Razor Pages and MVC Views in that they both use Razor. But unlike pages and views, which are built around a request/reply model, components are used specifically for handling UI composition.</p> <p>To create, build, and run your first ASP.NET Core app with Razor Components run the following from the command line:</p> <pre><code>dotnet <span class="hljs-keyword">new</span> razorcomponents -o WebApplication1 cd WebApplication1 dotnet <span class="hljs-built_in">run</span> </code></pre> <p>Or create an ASP.NET Core Razor Components in Visual Studio 2019:</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2.png" alt="Razor Components template" /></p> <p>The generated solution has two projects: a server project (WebApplication1.Server), and a project with client-side web UI logic written using Razor Components (WebApplication1.App). The server project is an ASP.NET Core project setup to host the Razor Components. </p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-1.png" alt="Razor Components solution" /></p> <p>Why two projects? In part it’s to separate the UI logic from the rest of the application. There is also a technical limitation in this preview that we are using the same Razor file extension (.cshtml) for Razor Components that we also use for Razor Pages and Views, but they have different compilation models, so they need to kept separate. In a future preview we plan to introduce a new file extension for Razor Components (.razor) so that you can easily host your components, pages, and views all in the same project.</p> <p>When you run the app you should see multiple pages (Home, Counter, and Fetch data) on different tabs. On the Counter page you can click a button to increment a counter without any page refresh. Normally this would require writing JavaScript, but here everything is written using Razor Components in C#!</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-2.png" alt="Razor Components app" /></p> <p>Here’s what the <code>Counter</code> component code looks like:</p> <pre><code class="lang-html"><span class="hljs-keyword">@page</span> <span class="hljs-string">"/counter"</span> <h1>Counter</h1> <p>Current count: <span class="hljs-keyword">@currentCount</span></p> <button <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-primary"</span> onclick=<span class="hljs-string">"@IncrementCount"</span>>Click me</button> <span class="hljs-keyword">@functions</span> { <span class="hljs-keyword">int</span> currentCount = <span class="hljs-number">0</span>; <span class="hljs-keyword">void</span> IncrementCount() { currentCount+=<span class="hljs-number">1</span>; } } </code></pre> <p>Making a request to <code>/counter</code>, as specified by the <code>@page</code> directive at the top, causes the component to render its content. Components render into an in-memory representation of the render tree that can then be used to update the UI in a very flexible and efficient way. Each time the “Click me” button is clicked the <code>onclick</code> event is fired and the <code>IncrementCount</code> method is called. The <code>currentCount</code> gets incremented and the component is rendered again. The runtime compares the newly rendered content with what was rendered previously and only the changes are then applied to the DOM (i.e. the updated count).</p> <p>You can use components from other components using an HTML-like syntax where component parameters are specified using attributes or child content. For example, you can add a <code>Counter</code> component to the app’s home page like this:</p> <pre><code class="lang-html"><span class="hljs-meta">@page</span> <span class="hljs-string">"/"</span> <span class="hljs-variable"><h1></span>Hello, world!<span class="hljs-variable"></h1></span> Welcome to your new app. <span class="hljs-variable"><Counter /></span> </code></pre> <p>To add a parameter to the <code>Counter</code> component update the <code>@functions</code> block to add a property decorated with the <code>[Parameter]</code> attribute:</p> <pre><code class="lang-csharp">@functions { <span class="hljs-keyword">int</span> currentCount = <span class="hljs-number">0</span>; [Parameter] <span class="hljs-keyword">int</span> IncrementAmount { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-number">1</span>; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">IncrementCount</span>(<span class="hljs-params" />) </span>{ currentCount+=IncrementAmount; } } </code></pre> <p>Now you can specify <code>IncrementAmount</code> parameter value using an attribute like this:</p> <pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">Counter</span> <span class="hljs-attr">IncrementAmount</span>=<span class="hljs-string">"10"</span> /></span> </code></pre> <p>The Home page then has it’s own counter that increments by tens:</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-3.png" alt="Count by tens" /></p> <p>This is just an intro to what Razor Components are capable of. Razor Components are based on the Blazor component model and they support all of the same features (parameters, child content, templates, lifecycle events, component references, etc.). To learn more about Razor Components check out the <a href="https://blazor.net/docs/components/index.html">component model docs</a> and <a href="https://blazor.net/docs/tutorials/build-your-first-blazor-app.html">try out building your first Razor Components app</a> yourself.</p> <h3 id="hosting-razor-components">Hosting Razor Components</h3> <p>Because Razor Components decouple a component’s rendering logic from how the UI updates get applied, there is a lot of flexibility in how Razor Components can be hosted. ASP.NET Core Razor Components in .NET Core 3.0 adds support for hosting Razor Components on the server in an ASP.NET Core app where all UI updates are handled over a SignalR connection. The runtime handles sending UI events from the browser to the server and then applies UI updates sent by the server back to the browser after running the components. The same connection is also used to handle JavaScript interop calls.</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-4.png" alt="ASP.NET Core Razor Components" /></p> <p>Alternatively, Blazor is an experimental single page app framework that runs Razor Components directly in the browser using a WebAssembly based .NET runtime. In Blazor apps the UI updates from the Razor Components are all applied in process directly to the DOM.</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-5.png" alt="Blazor" /></p> <p>Support for the client-side Blazor hosting model using WebAssembly won’t ship with ASP.NET Core 3.0, but we are working towards shipping it with a later release.</p> <p>Regardless of which hosting model you use for your Razor Components, the component model is the same. The same Razor Components can be used with either hosting model. You can even switch your app back and forth from being a client-side Blazor app or Razor Components running in ASP.NET Core using the same components as long as your components haven’t taken any server specific dependencies.</p> <h3 id="javascript-interop">JavaScript interop</h3> <p>Razor Components can also use client-side JavaScript if needed. From a Razor Component you can call into any browser API or into an existing JavaScript library running in the browser. .NET library authors can use JavaScript interop to provide .NET wrappers for JavaScript APIs, so that they can be conveniently called from Razor Components.</p> <pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> ExampleJsInterop { <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-built_in">Task</span><<span class="hljs-keyword">string</span>> Prompt(<span class="hljs-keyword">this</span> IJSRuntime js, <span class="hljs-keyword">string</span> <span class="hljs-built_in">text</span>) { <span class="hljs-comment">// showPrompt is implemented in wwwroot/exampleJsInterop.js</span> <span class="hljs-built_in">return</span> js.InvokeAsync<<span class="hljs-keyword">string</span>>(<span class="hljs-string">"exampleJsFunctions.showPrompt"</span>, <span class="hljs-built_in">text</span>); } } </code></pre> <pre><code class="lang-html">@inject IJSRuntime JS <<span class="hljs-keyword">button</span> <span class="hljs-keyword">onclick</span>=<span class="hljs-string">"@OnClick"</span>>Show prompt</<span class="hljs-keyword">button</span>> @functions { string name<span class="hljs-comment">;</span> async Task <span class="hljs-keyword">OnClick</span>() { name = <span class="hljs-keyword">await</span> JS.Prompt(<span class="hljs-string">"Hi! What's you're name?"</span>)<span class="hljs-comment">;</span> } } </code></pre> <p>Both Razor Components and Blazor share the same JavaScript interop abstraction, so .NET libraries relying on JavaScript interop are usable by both types of apps. Check out the <a href="https://blazor.net/docs/javascript-interop.html">JavaScript interop docs</a> for more details on using JavaScript interop and the <a href="https://blazor.net/community">Blazor community page</a> for existing JavaScript interop libraries.</p> <h3 id="sharing-component-libraries">Sharing component libraries</h3> <p>Components can be easily shared and reused just like you would normal .NET classes. Razor Components can be built into component libraries and then shared as NuGet packages. You can find existing component libraries on the <a href="https://blazor.net/community">Blazor community page</a>.</p> <p>The .NET Core 3.0 Preview 2 SDK doesn’t include a project template for Razor Component Class Libraries yet, but we expect to add one in a future preview. In meantime, you can use <a href="https://blazor.net/docs/components/component-libraries.html">Blazor Component Class Library template</a>.</p> <pre><code>dotnet new -<span class="hljs-selector-tag">i</span> Microsoft<span class="hljs-selector-class">.AspNetCore</span><span class="hljs-selector-class">.Blazor</span><span class="hljs-selector-class">.Templates</span> dotnet new blazorlib </code></pre> <p>In this preview release ASP.NET Core Razor Components don’t yet support using static assets in component libraries, so the support for component class libraries is pretty limited. However, in a future preview we expect to add this support for using static assets from a library just like you can in Blazor today.</p> <h3 id="integration-with-mvc-views-and-razor-pages">Integration with MVC Views and Razor Pages</h3> <p>Razor Components can be used with your existing Razor Pages and MVC apps. There is no need to rewrite existing views or pages to use Razor Components. Components can be used from within a view or page. When the page or view is rendered, any components used will be prerendered at the same time. </p> <p>To render a component from a Razor Page or MVC View in this release, use the <code>RenderComponentAsync<TComponent></code> HTML helper method:</p> <pre><code class="lang-html"><span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"Counter"</span>></span> @(await Html.RenderComponentAsync<span class="hljs-tag"><<span class="hljs-name">Counter</span>></span>(new </span><span class="hljs-template-variable">{ IncrementAmount = 10 }</span><span class="xml">)) <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span> </code></pre> <p>Components rendered from pages and views will be prerendered, but are not yet interactive (i.e. clicking the <code>Counter</code> button doesn’t do anything in this release). This will get addressed in a future preview, along with adding support for rendering components from pages and views using the normal element and attribute syntax.</p> <p>While views and pages can use components the converse is not true: components can’t use views and pages specific features, like partial views and sections. If you want to use a logic from partial view in a component you’ll need to factor that logic out first as a component.</p> <h3 id="cross-platform-tooling">Cross platform tooling</h3> <p>Visual Studio 2019 comes with built-in editor support for Razor Components including completions and diagnostics in the editor. You don’t need to install any additional extensions.</p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2.gif" alt="Razor Components tooling" /></p> <p>Razor Component tooling isn’t available yet in Visual Studio for Mac or Visual Studio Code, but it’s something we are actively working on.</p> <h3 id="a-bright-future-for-blazor">A bright future for Blazor</h3> <p>In parallel with the ASP.NET Core 3.0 work, we will continue ship updated experimental releases of Blazor to support hosting Razor Components client-side in the browser (we’ll have more to share on the latest Blazor update shortly!). While in ASP.NET Core 3.0 we will only support hosting Razor Components in ASP.NET Core, we are also working towards shipping Blazor and support for running Razor Components in the browser on WebAssembly in a future release.</p> <h2 id="signalr-client-to-server-streaming">SignalR client-to-server streaming</h2> <p>With ASP.NET Core SignalR we added <a href="https://docs.microsoft.com/aspnet/core/signalr/streaming">Streaming</a> support, which enables streaming return values from server-side methods. This is useful for when fragments of data will come in over a period of time. </p> <p>With .NET Core 3.0 Preview 2 we’ve added client-to-server streaming. With client-to-server streaming, your server-side methods can take instances of a <code>ChannelReader<T></code>. In the C# code sample below, the <code>StartStream</code> method on the <a href="https://docs.microsoft.com/aspnet/core/signalr/hubs">Hub</a> will receive a stream of strings from the client. </p> <pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">StartStream</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> streamName, ChannelReader<<span class="hljs-keyword">string</span>> streamContent</span>) </span>{ <span class="hljs-comment">// read from and process stream items</span> <span class="hljs-keyword">while</span> (<span class="hljs-keyword">await</span> streamContent.WaitToReadAsync(Context.ConnectionAborted)) { <span class="hljs-keyword">while</span> (streamContent.TryRead(<span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> content)) { <span class="hljs-comment">// process content</span> } } } </code></pre> <p>Clients would use the SignalR <code>Subject</code> (or an <a href="https://rxjs-dev.firebaseapp.com/guide/subject">RxJS Subject</a>) as an argument to the <code>streamContent</code> parameter of the Hub method above.</p> <pre><code class="lang-javascript"><span class="hljs-keyword">let</span> subject = <span class="hljs-keyword">new</span> signalR.Subject(); <span class="hljs-keyword">await</span> connection.send(<span class="hljs-string">"StartStream"</span>, <span class="hljs-string">"MyAsciiArtStream"</span>, subject); </code></pre> <p>The JavaScript code would then use the <code>subject.next</code> method to handle strings as they are captured and ready to be sent to the server. </p> <pre><code class="lang-javascript">subject.next(<span class="hljs-string">"example"</span>)<span class="hljs-comment">;</span> subject.complete()<span class="hljs-comment">;</span> </code></pre> <p>Using code like the two snippets above, you can create real-time streaming experiences. For a preview of what you can do with client-side streaming with SignalR, take a look at the demo site, <a href="http://streamr.azurewebsites.net">streamr.azurewebsites.net</a>. If you create your own stream, you can stream ASCII art representations of image data being captured by your local web cam to the server, where it will be bounced out to other clients who are watching your stream. </p> <p><img src="http://www.sickgaming.net/blog/wp-content/uploads/2019/01/asp-net-core-updates-in-net-core-3-0-preview-2-6.png" alt="Client-to-server Streaming with SignalR" /></p> <h2 id="system-io-pipelines-on-httpcontext">System.IO.Pipelines on HttpContext</h2> <p>In ASP.NET Core 3.0, we’re working on consuming the <a href="https://blogs.msdn.microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io-in-net/"><code>System.IO.Pipelines</code> API</a> and exposing it in ASP.NET Core to allow you to write more performant applications.</p> <p>In Preview 2, we’re exposing the request body pipe and response body pipe on the <code>HttpContext</code> that you can directly read from and write to respectively in addition to maintaining the existing Stream-based APIs.<br />While these pipes are currently just wrappers over the existing streams, we will directly expose the underlying pipes in a future preview.</p> <p>Here’s an example that demonstrates using both the request body and response body pipes directly.</p> <pre><code class="lang-csharp">public <span class="hljs-keyword">void</span> Configure(IApplicationBuilder app, IHostingEnvironment env) { <span class="hljs-keyword">if</span> (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(routes => { routes.MapGet(<span class="hljs-string">"/"</span>, <span class="hljs-keyword">async</span> context => { <span class="hljs-keyword">await</span> context.Response.WriteAsync(<span class="hljs-string">"Hello World"</span>); }); routes.MapPost(<span class="hljs-string">"/"</span>, <span class="hljs-keyword">async</span> context => { <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) { <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> context.Request.BodyPipe.ReadAsync(); <span class="hljs-keyword">var</span> buffer = result.Buffer; <span class="hljs-keyword">if</span> (result.IsCompleted) { <span class="hljs-keyword">break</span>; } context.Request.BodyPipe.AdvanceTo(buffer.End); } }); }); } </code></pre> <h2 id="generic-host-in-templates">Generic host in templates</h2> <p>The templates have been updated to use the <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2">Generic Host</a> instead of <code>WebHostBuilder</code> as they have in the past:</p> <pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span><span class="hljs-params">(<span class="hljs-built_in">string</span>[] args)</span> </span>{ CreateHostBuilder(args).Build().Run(); } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IHostBuilder <span class="hljs-title">CreateHostBuilder</span><span class="hljs-params">(<span class="hljs-built_in">string</span>[] args)</span> </span>=> Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); </code></pre> <p>This is part of the ongoing plan started in 2.0 to better integrate ASP.NET Core with other server scenarios that are not web specific.</p> <h3 id="what-about-iwebhostbuilder-">What about <code>IWebHostBuilder</code>?</h3> <p>The <code>IWebHostBuilder</code> interface that is used with <code>WebHostBuilder</code> today will be kept, and is the type of the <code>webBuilder</code> used in the sample code above. We intend to deprecate and eventually remove <code>WebHostBuilder</code> itself as its functionality will be replaced by <code>HostBuilder</code>, though the interface will remain.</p> <p>The biggest difference between <code>WebHostBuilder</code> and <code>HostBuilder</code> is that you can no longer inject arbitrary services into your <code>Startup.cs</code>. Instead you will be limited to the <code>IHostingEnvironment</code> and <code>IConfiguration</code> interfaces. This removes a behavior quirk related to injecting services into <code>Startup.cs</code> before the <code>ConfigureServices</code> method is called. We will publish more details on the differences between <code>WebHostBuilder</code> and <code>HostBuilder</code> in a future deep-dive post.</p> <h2 id="endpoint-routing-updates">Endpoint routing updates</h2> <p>We’re excited to start introducing more of the Endpoint Routing story that began in 2.2. Endpoint routing allows frameworks like MVC as well as other <em>routable</em> things to mix with middleware in a way that hasn’t been possible before. This is now present in the project templates in 3.0.0-preview-2, we’ll continue to add more richness as we move closer to a final release.</p> <p>Here’s an example:</p> <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage()<span class="hljs-comment">;</span> } app.UseStaticFiles()<span class="hljs-comment">;</span> app.UseRouting(routes => { routes.MapApplication()<span class="hljs-comment">;</span> routes.MapGet(<span class="hljs-string">"/hello"</span>, <span class="hljs-built_in">context</span> => { return <span class="hljs-built_in">context</span>.Response.WriteAsync(<span class="hljs-string">"Hi there!"</span>)<span class="hljs-comment">; </span> })<span class="hljs-comment">;</span> routes.MapHealthChecks(<span class="hljs-string">"/healthz"</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span> app.UseAuthentication()<span class="hljs-comment">;</span> app.UseAuthorization()<span class="hljs-comment">;</span> } </code></pre> <p>There’s a few things to unpack here. </p> <p>First, the <code>UseRouting(...)</code> call adds a new Endpoint Routing middleware. <code>UseRouting</code> is at the core of many of the templates in 3.0 and replaces many of the features that were implemented inside <code>UseMvc(...)</code> in the past. </p> <p>Also notice that inside <code>UseRouting(...)</code> we’re setting up a few things. <code>MapApplication()</code> brings in MVC controllers and pages for routing. <code>MapGet(...)</code> shows how to wire up a request delegate to routing. <code>MapHealthChecks(...)</code> hooks up the health check middleware, but by plugging it into routing.</p> <p>What might be surprising to see is that some middleware now come after <code>UseRouting</code>. Let’s tweak this example to demonstrate why that is valuable.</p> <pre><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage()<span class="hljs-comment">;</span> } app.UseStaticFiles()<span class="hljs-comment">;</span> app.UseRouting(routes => { routes.MapApplication()<span class="hljs-comment">;</span> routes.MapGet(<span class="hljs-string">"/hello"</span>, <span class="hljs-built_in">context</span> => { return <span class="hljs-built_in">context</span>.Response.WriteAsync(<span class="hljs-string">"Hi there! Here's your secret message"</span>)<span class="hljs-comment">; </span> }) .RequireAuthorization(new AuthorizeAttribute(){ Roles = <span class="hljs-string">"secret-messages"</span>, })<span class="hljs-comment">;</span> routes.MapHealthChecks(<span class="hljs-string">"/healthz"</span>).RequireAuthorization(<span class="hljs-string">"admin"</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span> app.UseAuthentication()<span class="hljs-comment">;</span> app.UseAuthorization()<span class="hljs-comment">;</span> } </code></pre> <p>Now I’ve added an <code>AuthorizeAttribute</code> to my request delegate. This is just like placing <code>[Authorize(Roles = "secret-messages")]</code> on an action method in a controller. We’ve also given the health checks middleware an authorization policy as well (by policy name).</p> <p>This works because the following steps happen in order (ignoring what happens before routing):</p> <ol> <li><code>UseRouting(...)</code> makes a routing decision – selecting an <em>Endpoint</em></li> <li><code>UseAuthorization()</code> looks at the <em>Endpoint</em> that was selected and runs the corresponding authorization policy</li> <li><em>hidden</em>… At the end of the middleware pipeline the <em>Endpoint</em> is executed (if no endpoint was matched then a 404 response is returned)</li> </ol> <p>So think of <code>UseRouting(...)</code> as making a <em>deferred routing decision</em> – where middleware that appear after it run in <em>the middle</em>. Any middleware that run after routing can see the results and read or modify the route data and chosen endpoint. When processing reaches the end of the pipeline, then the endpoint is invoked.</p> <h3 id="what-is-an-endpoint-and-why-did-we-add-this-">What is an Endpoint and why did we add this?</h3> <p>An <em>Endpoint</em> is a new primitive to help frameworks (like MVC) be friends with middleware. Fundamentally an <em>Endpoint</em> is a request delegate (something that can execute) plus a bag of metadata (policies). </p> <p>Here’s an example middleware – you can use this to examine endpoints in the debugger or by printing to the console:</p> <pre><code>app.Use(<span class="hljs-keyword">next</span> => (context) => { var endpoint = context.GetEndpoint(); <span class="hljs-keyword">if</span> (endpoint != <span class="hljs-keyword">null</span>) { Console.<span class="hljs-keyword">WriteLine</span>(<span class="hljs-string">"Name: "</span> + endpoint.DisplayName); Console.<span class="hljs-keyword">WriteLine</span>(<span class="hljs-string">"Route: "</span> + (endpoint as RouteEndpoint)?.RoutePattern); Console.<span class="hljs-keyword">WriteLine</span>(<span class="hljs-string">"Metadata: "</span> + string.<span class="hljs-keyword">Join</span>(<span class="hljs-string">", "</span>, endpoint.Metadata)); } <span class="hljs-keyword">return</span> <span class="hljs-keyword">next</span>(context); }); </code></pre> <p>In the past we haven’t had a good solution when we’ve wanted to implement a policy like CORS or Authorization in both middleware and MVC. Putting a middleware in the pipeline feels very good because you get to configure the order. Putting filters and attributes on methods in controllers feels really good when you need to apply policies to different parts of the application. Endpoints bring togther all of these advantages. </p> <p>As an addition problem – what do you do if you’re writing the health checks middleware? You might want to secure your middleware in a way that developers can customize. Being able to leverage the ASP.NET Core features for this directly avoids the need to build in support for cross-cutting concerns in every component that serves HTTP.</p> <p>In addition to removing code duplication from MVC, the Endpoint + Middleware solution can be used by any other ASP.NET Core-based technologies. You don’t even need to use <code>UseRouting(...)</code> – all that is required to leverage the enhancements to middleware is to set an <em>Endpoint</em> on the <code>HttpContext</code>.</p> <h3 id="what-s-integrated-with-this-">What’s integrated with this?</h3> <p>We added the new authorize middleware so that you can start doing more powerful security things with just middleware. The authorize middleware can accept a default policy that applies when there’s no endpoint, or the endpoint doesn’t specify a policy.</p> <p>CORS is also now endpoint routing aware and will use the CORS policy specified on an endpoint. </p> <p>MVC also plugs in to endpoint routing and will create endpoints for all of your controllers and pages. MVC can now be used with the CORS and authorize features and will largely work the same. We’ve long had confusion about whether to use the CORS middleware or CORS filters in MVC, the updated guidance is to use both. This allows you to provide CORS support to other middleware or static files, while still applying more granular CORS policies with the existing attributes.</p> <p>Health checks also provide methods to register the health checks middleware as a router-ware (as shown above). This allows you to specify other kinds of policies for health checks.</p> <p>Finally, new in ASP.NET Core 3.0 preview 2 is host matching for routes. Placing the <code>HostAttribute</code> on an MVC controller or action will prompt the routing system to require the specified domain or port. Or you can use <code>RequireHost</code> in your <code>Startup.cs</code>:</p> <pre><code>app.UseRouting(routes => { routes.MapGet(<span class="hljs-string">"/"</span>, <span class="hljs-built_in">context</span> => <span class="hljs-built_in">context</span>.Response.WriteAsync(<span class="hljs-string">"Hi Contoso!"</span>)) .RequireHost(<span class="hljs-string">"contoso.com"</span>)<span class="hljs-comment">;</span> routes.MapGet(<span class="hljs-string">"/"</span>, <span class="hljs-built_in">context</span> => <span class="hljs-built_in">context</span>.Response.WriteAsync(<span class="hljs-string">"Hi AdventureWorks!"</span>)) .RequireHost(<span class="hljs-string">"adventure-works.com"</span>)<span class="hljs-comment">;</span> routes.MapHealthChecks(<span class="hljs-string">"/healthz"</span>).RequireHost(<span class="hljs-string">"*:8080"</span>)<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span> </code></pre> <p>Do you think that there are things that are missing from the endpoint story? Are there more things we should make smarter or more integrated? Please let us know what you’d like to see. </p> <h2 id="give-feedback">Give feedback</h2> <p>We hope you enjoy the new features in this preview release of ASP.NET Core! Please let us know what you think by filing issues on <a href="https://github.com/aspnet/aspnetcore/issues">Github</a>.</p> <p> </p> </div> |