08-28-2018, 02:29 AM
ASP.NET Core 2.2.0-preview1: Endpoint Routing
<div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" width="549" height="365" title="" alt="" /></div><div><h2>What is it?</h2>
<p>We’re making a big investment in routing starting in 2.2 to make it interoperate more seamlessly with middleware. For 2.2 this will start with us making a few changes to the routing model, and adding some minor features. In 3.0 the plan is to introduce a model where routing and middleware operate together naturally. This post will focus on the 2.2 improvements, we’ll discuss 3.0 a bit further in the future.</p>
<p>So, without further ado, here are some changes coming to routing in 2.2.</p>
<h2>How to use it?</h2>
<p>The new routing features will be on by default for 2.2 applications using MVC. <code><span>UseMvc</span></code> and related methods with the 2.2 compatibility version will enable the new ‘Endpoint Routing’ feature set. Existing conventional routes (using <code><span>MapRoute</span></code>) or attribute routes will be mapped into the new system.</p>
<pre><code><span>public void ConfigureServices(IServiceProvider services)</span>
<span>{</span>
<span> ...</span>
<span> </span>
<span> services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);</span>
<span>}</span>
<span />
<span>public void Configure(IApplicationBuilder app)</span>
<span>{</span>
<span> ....</span>
<span> </span>
<span> app.UseMvc();</span>
<span>}
</span></code></pre>
<p>If you need to specifically revert the new routing features, this can be done by setting <a href="https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs#L99">an option</a>. We’ve tried to make the new endpoint routing system as backwards compatible as is feasible. Please log issues at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a> if you encounter problems.</p>
<p>We don’t plan to provide an experience in 2.2 for using the new features without MVC – our focus for right now is to make sure we can successfully shift MVC applications to the new infrastructure.</p>
<h2>Link Generator Service</h2>
<p>We’re introducing a new singleton service that will support generating a URL. This new service can be used from middleware, and does not require an <code><span>HttpContext</span></code>. For right now the set of things you can link to is limited to MVC actions, but this will expand in 3.0.</p>
<pre><code><span>public class MyMiddleware</span>
<span>{</span>
<span> public MyMiddleware(RequestDelegate next, LinkGenerator linkGenerator) { ... }</span>
<span> </span>
<span> public async Task Invoke(HttpContext httpContext)</span>
<span> {</span>
<span> var url = _linkGenerator.GenerateLink(new { controller = "Store", action = "ListProducts" });</span>
<span> </span>
<span> httpContext.Response.ContentType = "text/plain";</span>
<span> return httpContext.Response.WriteAsync($"Go to {url} to see some cool stuff.");</span>
<span> }</span>
<span>}
</span></code></pre>
<p>This looks a lot like MVC’s link generation support (<code><span>IUrlHelper</span></code>) for now — but it’s usable anywhere in your application. We plan to expand the set of things that are possible during 2.2.</p>
<h2>Performance Improvements</h2>
<p>One of the biggest reasons for us to revisit routing in 2.2 is to improve the performance of routing and MVC’s action selection.</p>
<p>We still have more work to, but the results so far are promising:<br /><a target="_blank" href="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" rel="noopener"><img alt="image" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" /></a></p>
<p>This chart shows the trend of Requests per Second (RPS) of our MVC implementation of the TechEmpower plaintext benchmark. We’ve improved the RPS of this benchmark about 10% from 445kRPS to 515kRPS.</p>
<p>To test more involved scenarios, we’ve used the Swagger/OpenAPI files published by <a href="https://github.com/aspnet/Routing/blob/release/2.2/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherAzureBenchmark.generated.cs">Azure</a> and <a href="https://github.com/aspnet/Routing/blob/release/2.2/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherGithubBenchmark.generated.cs">Github</a> to build code-generated routing benchmarks. The Azure API benchmark has 5160 distinct HTTP endpoints, and the Github benchmark has 243. The new routing system is significantly faster at processing the route table of these real world APIs than anything we’ve had before. In particular MVC’s features that select actions such as matching on HTTP methods and <code><span>[Consumes(...)]</span></code> are significantly less costly.</p>
<h2>Improvements to link generation behavior for attribute routing</h2>
<p>We’re also using this opportunity to revisit some of the behaviors that users find confusing when using attribute routing. Razor Pages is based on MVC’s attribute routing infrastructure and so many new users have become familiar with these problems lately. Inside the team, we refer to this feature as ‘route value invalidation’.</p>
<p>Without getting into too many details, conventional routing always invalidates extra route values when linking to another action. Attribute routing didn’t have this behavior in the past. This can lead to mistakes when linking to another action that uses the same route parameter names. Now both forms of routing invalidate values when linking to another action.</p>
<h2>A conceptual understanding</h2>
<p>The new routing system is called ‘Endpoint Routing’ because it represents the route table as a set of Endpoints that can be selected by the the routing system. If you’ve ever thought about how attribute routing might work in MVC, the above description should not be surprising. The new part is that a bunch of concerns traditionally handled by MVC have been pushed down to a very low level of the routing system. Endpoint routing now processes HTTP methods, <code><span>[Consumes(...)]</span></code>, versioning, and other policies that used to be part of MVC’s action selection process.</p>
<p>In contrast to this, the existing routing system models the application is a list of ‘Routes’ that need to be processed in order. What each route does is a black-box to the routing system – you have to run the route to see if it will match.</p>
<p>To make MVC’s conventional routing work, we flatten the list of actions multiplied by the number of routes into a list of endpoints. This flattening allows us to process all of MVC’s requirements very efficiently inside the routing system.</p>
<hr />
<p>The list of endpoints gets compiled into a tree that’s easy for us to walk efficiently. This is similar to what attribute routing does today but using a different algorithm and much lower complexity. Since routing builds a graph based on the endpoints, this means that the complexity of the tree scales very directly with your usage of features. We’re confident that we can scale up this design nicely while retaining the pay-for-play characteristics.</p>
<h2>New round-tripping route parameter syntax</h2>
<p>We are introducing a new catch-all parameter syntax <code><span>{**myparametername}</span></code>. During link generation, the routing system will encode all the content in the value captured by this parameter except the forward slashes.<br />Note that the old parameter syntax <code><span>{*myparametername}</span></code> will continue to work as it did before. Examples:</p>
<ul>
<li>For a route defined using the old parameter syntax : <code><span>/search/{*page}</span></code>,<br />a call to <code><span>Url.Action(new { category = "admin/products" })</span></code> would generate a link <code><span>/search/admin%2Fproducts</span></code> (notice that the forward slash is encoded)</li>
<li>For a route defined using the new parameter syntax : <code><span>/search/{**page}</span></code>,<br />a call to <code><span>Url.Action(new { category = "admin/products" })</span></code> would generate a link <code><span>/search/admin/products</span></code></li>
</ul>
<h2>What is coming next?</h2>
<p>Expect more refinement and polish on the <code><span>LinkGenerator</span></code> API in the next preview. We want to make sure that this new API will support a variety of scenarios for the foreseeable future.</p>
<h2>How can you help?</h2>
<p>There are a few areas where you can provide useful feedback during this preview. We’re interested in any thoughts you have of course, these are a few specific things we’d like opinions on. The best place to provide feedback is by opening issues at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a></p>
<p>What are you using <code><span>IRouter</span></code> for? The ‘Endpoint Routing’ system doesn’t support <code><span>IRouter</span></code>-based extensibility, including inheriting from <code><span>Route</span></code>. We want what you’re using <code><span>IRouter</span></code> for today so we can figure out how to accomplish those things in the future.</p>
<p>What are you using <code><span>IActionConstraint</span></code> for? ‘Endpoint Routing’ supports <code><span>IActionConstraint</span></code>-based extensibility from MVC, but we’re trying to find better ways to accomplish these tasks.</p>
<p>What are your ‘most wanted’ issues from Routing? We’ve revisited a bunch of old closed bugs and brought back a few for reconsideration. If you feel like there are missing details or bugs in routing that we should consider please let us know.</p>
<h2>Caveats and notes</h2>
<p>We’ve worked to make the new routing system backwards compatible where possible. If you run into issues we’d love for you to report them at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a>.</p>
<p>DataTokens are not supported in 2.2.0-preview1. We plan to address this for the next preview.</p>
<p>By nature the new routing system does not support <code><span>IRouter</span></code> based extensibility.</p>
<p>Generating links inside MVC to conventionally routed actions that don’t yet exist will fail (resulting in the empty string). This is in contrast to the current MVC behavior where linking usually succeeds even if the action being linked hasn’t been defined yet.</p>
<p>We know that the performance of link generation will be bad in 2.2.0-preview1. We worked hard to get the API definitions in so that you could try it out, and ignored performance. Expect the performance of URL generation to improve significantly for the next preview.</p>
<p>Endpoint routing does not support <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.WebApiCompatShim" rel="nofollow">WebApiCompatShim</a>. You must use the 2.1 compatibility switch to continue using the compat shim.</p>
<p> </p>
</div>
<div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" width="549" height="365" title="" alt="" /></div><div><h2>What is it?</h2>
<p>We’re making a big investment in routing starting in 2.2 to make it interoperate more seamlessly with middleware. For 2.2 this will start with us making a few changes to the routing model, and adding some minor features. In 3.0 the plan is to introduce a model where routing and middleware operate together naturally. This post will focus on the 2.2 improvements, we’ll discuss 3.0 a bit further in the future.</p>
<p>So, without further ado, here are some changes coming to routing in 2.2.</p>
<h2>How to use it?</h2>
<p>The new routing features will be on by default for 2.2 applications using MVC. <code><span>UseMvc</span></code> and related methods with the 2.2 compatibility version will enable the new ‘Endpoint Routing’ feature set. Existing conventional routes (using <code><span>MapRoute</span></code>) or attribute routes will be mapped into the new system.</p>
<pre><code><span>public void ConfigureServices(IServiceProvider services)</span>
<span>{</span>
<span> ...</span>
<span> </span>
<span> services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);</span>
<span>}</span>
<span />
<span>public void Configure(IApplicationBuilder app)</span>
<span>{</span>
<span> ....</span>
<span> </span>
<span> app.UseMvc();</span>
<span>}
</span></code></pre>
<p>If you need to specifically revert the new routing features, this can be done by setting <a href="https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs#L99">an option</a>. We’ve tried to make the new endpoint routing system as backwards compatible as is feasible. Please log issues at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a> if you encounter problems.</p>
<p>We don’t plan to provide an experience in 2.2 for using the new features without MVC – our focus for right now is to make sure we can successfully shift MVC applications to the new infrastructure.</p>
<h2>Link Generator Service</h2>
<p>We’re introducing a new singleton service that will support generating a URL. This new service can be used from middleware, and does not require an <code><span>HttpContext</span></code>. For right now the set of things you can link to is limited to MVC actions, but this will expand in 3.0.</p>
<pre><code><span>public class MyMiddleware</span>
<span>{</span>
<span> public MyMiddleware(RequestDelegate next, LinkGenerator linkGenerator) { ... }</span>
<span> </span>
<span> public async Task Invoke(HttpContext httpContext)</span>
<span> {</span>
<span> var url = _linkGenerator.GenerateLink(new { controller = "Store", action = "ListProducts" });</span>
<span> </span>
<span> httpContext.Response.ContentType = "text/plain";</span>
<span> return httpContext.Response.WriteAsync($"Go to {url} to see some cool stuff.");</span>
<span> }</span>
<span>}
</span></code></pre>
<p>This looks a lot like MVC’s link generation support (<code><span>IUrlHelper</span></code>) for now — but it’s usable anywhere in your application. We plan to expand the set of things that are possible during 2.2.</p>
<h2>Performance Improvements</h2>
<p>One of the biggest reasons for us to revisit routing in 2.2 is to improve the performance of routing and MVC’s action selection.</p>
<p>We still have more work to, but the results so far are promising:<br /><a target="_blank" href="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" rel="noopener"><img alt="image" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/08/asp-net-core-2-2-0-preview1-endpoint-routing.png" /></a></p>
<p>This chart shows the trend of Requests per Second (RPS) of our MVC implementation of the TechEmpower plaintext benchmark. We’ve improved the RPS of this benchmark about 10% from 445kRPS to 515kRPS.</p>
<p>To test more involved scenarios, we’ve used the Swagger/OpenAPI files published by <a href="https://github.com/aspnet/Routing/blob/release/2.2/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherAzureBenchmark.generated.cs">Azure</a> and <a href="https://github.com/aspnet/Routing/blob/release/2.2/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherGithubBenchmark.generated.cs">Github</a> to build code-generated routing benchmarks. The Azure API benchmark has 5160 distinct HTTP endpoints, and the Github benchmark has 243. The new routing system is significantly faster at processing the route table of these real world APIs than anything we’ve had before. In particular MVC’s features that select actions such as matching on HTTP methods and <code><span>[Consumes(...)]</span></code> are significantly less costly.</p>
<h2>Improvements to link generation behavior for attribute routing</h2>
<p>We’re also using this opportunity to revisit some of the behaviors that users find confusing when using attribute routing. Razor Pages is based on MVC’s attribute routing infrastructure and so many new users have become familiar with these problems lately. Inside the team, we refer to this feature as ‘route value invalidation’.</p>
<p>Without getting into too many details, conventional routing always invalidates extra route values when linking to another action. Attribute routing didn’t have this behavior in the past. This can lead to mistakes when linking to another action that uses the same route parameter names. Now both forms of routing invalidate values when linking to another action.</p>
<h2>A conceptual understanding</h2>
<p>The new routing system is called ‘Endpoint Routing’ because it represents the route table as a set of Endpoints that can be selected by the the routing system. If you’ve ever thought about how attribute routing might work in MVC, the above description should not be surprising. The new part is that a bunch of concerns traditionally handled by MVC have been pushed down to a very low level of the routing system. Endpoint routing now processes HTTP methods, <code><span>[Consumes(...)]</span></code>, versioning, and other policies that used to be part of MVC’s action selection process.</p>
<p>In contrast to this, the existing routing system models the application is a list of ‘Routes’ that need to be processed in order. What each route does is a black-box to the routing system – you have to run the route to see if it will match.</p>
<p>To make MVC’s conventional routing work, we flatten the list of actions multiplied by the number of routes into a list of endpoints. This flattening allows us to process all of MVC’s requirements very efficiently inside the routing system.</p>
<hr />
<p>The list of endpoints gets compiled into a tree that’s easy for us to walk efficiently. This is similar to what attribute routing does today but using a different algorithm and much lower complexity. Since routing builds a graph based on the endpoints, this means that the complexity of the tree scales very directly with your usage of features. We’re confident that we can scale up this design nicely while retaining the pay-for-play characteristics.</p>
<h2>New round-tripping route parameter syntax</h2>
<p>We are introducing a new catch-all parameter syntax <code><span>{**myparametername}</span></code>. During link generation, the routing system will encode all the content in the value captured by this parameter except the forward slashes.<br />Note that the old parameter syntax <code><span>{*myparametername}</span></code> will continue to work as it did before. Examples:</p>
<ul>
<li>For a route defined using the old parameter syntax : <code><span>/search/{*page}</span></code>,<br />a call to <code><span>Url.Action(new { category = "admin/products" })</span></code> would generate a link <code><span>/search/admin%2Fproducts</span></code> (notice that the forward slash is encoded)</li>
<li>For a route defined using the new parameter syntax : <code><span>/search/{**page}</span></code>,<br />a call to <code><span>Url.Action(new { category = "admin/products" })</span></code> would generate a link <code><span>/search/admin/products</span></code></li>
</ul>
<h2>What is coming next?</h2>
<p>Expect more refinement and polish on the <code><span>LinkGenerator</span></code> API in the next preview. We want to make sure that this new API will support a variety of scenarios for the foreseeable future.</p>
<h2>How can you help?</h2>
<p>There are a few areas where you can provide useful feedback during this preview. We’re interested in any thoughts you have of course, these are a few specific things we’d like opinions on. The best place to provide feedback is by opening issues at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a></p>
<p>What are you using <code><span>IRouter</span></code> for? The ‘Endpoint Routing’ system doesn’t support <code><span>IRouter</span></code>-based extensibility, including inheriting from <code><span>Route</span></code>. We want what you’re using <code><span>IRouter</span></code> for today so we can figure out how to accomplish those things in the future.</p>
<p>What are you using <code><span>IActionConstraint</span></code> for? ‘Endpoint Routing’ supports <code><span>IActionConstraint</span></code>-based extensibility from MVC, but we’re trying to find better ways to accomplish these tasks.</p>
<p>What are your ‘most wanted’ issues from Routing? We’ve revisited a bunch of old closed bugs and brought back a few for reconsideration. If you feel like there are missing details or bugs in routing that we should consider please let us know.</p>
<h2>Caveats and notes</h2>
<p>We’ve worked to make the new routing system backwards compatible where possible. If you run into issues we’d love for you to report them at <a href="https://github.com/aspnet/Routing">https://github.com/aspnet/Routing</a>.</p>
<p>DataTokens are not supported in 2.2.0-preview1. We plan to address this for the next preview.</p>
<p>By nature the new routing system does not support <code><span>IRouter</span></code> based extensibility.</p>
<p>Generating links inside MVC to conventionally routed actions that don’t yet exist will fail (resulting in the empty string). This is in contrast to the current MVC behavior where linking usually succeeds even if the action being linked hasn’t been defined yet.</p>
<p>We know that the performance of link generation will be bad in 2.2.0-preview1. We worked hard to get the API definitions in so that you could try it out, and ignored performance. Expect the performance of URL generation to improve significantly for the next preview.</p>
<p>Endpoint routing does not support <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.WebApiCompatShim" rel="nofollow">WebApiCompatShim</a>. You must use the 2.1 compatibility switch to continue using the compat shim.</p>
<p> </p>
</div>