ASP.NET Core 2.2 First Look – Endpoint Routing (aka Dispatcher) What is Endpoint Routing in ASP.NET Core 2.2?

The first preview of ASP.NET Core 2.2 is due (very) soon and it will be our first chance to test the changes and new features which are expected to be released by the end of this year.

ASP.NET Core 2.2 marks the next minor release of the ASP.NET Core framework, following on from the ASP.NET Core 2.1 release at the end of May. With these minor releases, the team are able to provide new features to us quickly and iterate on an already great product. ASP.NET Core 2.2 will be a “Current” release for those wanting to take advantage of the latest features more quickly. For some, the “current” releases may not be suitable as while offering newer features they do not offer the same long support commitment as long-term support (LTS). If you choose the “current” release train then you need to keep updating as new “current” releases are available (See the ASP.NET Core support policy for more details).

I’ve been watching the features planned for ASP.NET Core 2.2 since version 2.1 shipped. There are quite a few things which look pretty interesting. Today I want to take an early look at a new feature whose final name is Global Endpoint Routing. You may also see this referred to by its former name “Dispatcher”.

Update 18-08-2018: James Newton-King who works on the ASP.NET team, has confirmed that the name for this feature with be Endpoint routing.

NOTE: This post is based on reading and watching some limited early coverage of Dispatcher (Global Routing) from the ASP.NET team over the last few months. I’ve also spent a small amount of time looking at the source code and some early samples to form the content below. As a result, there are things I may not have 100% correct! This post is written prior to the first preview of ASP.NET Core 2.2 and as a result, some of this could change before the final release of ASP.NET Core 2.2. If you’re reading this in the future, treat it with some caution and keep an eye out for more recent posts where I will cover using the released versions of the feature. If you’re reading this in the past, I’m pleased to see that time travel is now a thing!!

Update 18-08-2018: After feedback from James Newton-King who works on the ASP.NET team, I understand that in the 2.2 timeframe the only supported way to use Endpoint Routing will be via MVC.

What is Global Endpoint Routing (the feature formerly known as Dispatcher)?

The current routing story with ASP.NET Core 2.1 MVC is that we define routes within MVC using either convention-based routing which uses a RouteBuilder when we register MVC into the middleware pipeline or via Route Attributes on our action methods. Personally, I tend towards the attribute approach for the APIs I build.

When a request reaches MVC, the route information is used to determine which Controller and Action should be invoked to handle the request, based on the URL path. At this point, MVC can invoke that action which will then return the response. If a suitable route is not found then a 404 is returned instead.

One issue with this approach to routing is that as the request flows through the middleware chain, all other middleware has no idea of where that request is eventually going to be handled.

With the Global Endpoint Routing feature, routing decisions can occur earlier into the pipeline so that incoming requests can be mapped to their eventual endpoint before MVC is even invoked. This gives other middleware an opportunity to make more reasoned choices and process requests armed with the knowledge of which Endpoint will eventually handle the request.

Implemented as middleware the URL matching can now be registered to run very early in the application middleware pipeline. After which, a set of known Endpoints will be registered. When a request is passed through the pipeline, other middleware will be able to inspect the final matched Endpoint and view/update its associated metadata.

In addition, Global Endpoint Routing looks like it should allow developers to build ASP.NET Core applications that don’t use MVC, but which can still rely on routing to handle requests. Developers can provide their own EndpointDataSource, complete with a set of route patterns and the corresponding RequestDelegates to be invoked for matches. There are opportunities here for some very lightweight APIs to be built without the sometimes unnecessary additional overhead of MVC.

Note: Based on the feedback from James Newton-King this feature is expected to work with MVC as the support way to use it in 2.2. For the 3.0 timeframe a public API for non MVC use is expected to be ready.

The original ASP.NET Core 2.2 features announcement (where the feature is known as “Dispatcher”) included a good example of where this Global Endpoint Routing concept can be useful. CORS is currently implemented as both a feature of MVC and as middleware. This is necessary as some requests will be handled outside of MVC, such as serving static files. However, to apply CORS for requests which do reach MVC, the policy can’t be applied until the MVC routing selects the controller and action for the request. Global Routing will enable CORS to be implemented more consistently since the routing and Endpoints will be known up front when a request is matched through the middleware.

Global Endpoint Routing is also integrated with the latest ASP.NET Core 2.2 MVC functionality, allowing MVC to work on top of this new Global Endpoint Routing feature. MVC in ASP.NET Core 2.2 includes code changes to support building up a list of available Endpoints for Global Endpoint Routing, using the existing MVC route provider.

I plan to dive into all of this more deeply in future posts. I’m currently spending a fair bit of time in the Microsoft.AspNetCore.Routing source code understanding the building blocks and Types involved.

A Small Sample

I’ll end with a basic example of what the services and middleware pipeline for an ASP.NET Core 2.2 application using Global Endpoint Routing could look like. In this case, there’s no MVC involved (this is based on the current RoutingSample from the Routing repository).

Update 18-08-2018: After feedback from James Newton-King who works on the ASP.NET team, I understand that in the 2.2 timeframe the only supported way to use Endpoint Routing will be via MVC.

Inside the Startup class, we must register the routing services in the ConfigureServices method as follows…

This sample manually adds a DefaultEndpointDataSource with a single Endpoint which is not necessary if you wish to use the MVC routes. MVC will populate Endpoints for you.

Once registered we can add Global Endpoint Routing to the pipeline as middleware in the Configure method…

The first middleware, UseEndpointRouting is responsible for inspecting the incoming request and matching it to an Endpoint. The matched Endpoint is set in the HttpContext.Features collection. At this point, all other middleware can retrieve the Endpoint from the Features collection as necessary.

Since we’re not using MVC in this sample we need something to invoke the matched RequestDelegate for the Endpoint. UseEndpoint registers the middleware which does exactly that.

Summary

Hopefully, this has been a useful primer of what to expect with the Global Endpoint Routing feature in ASP.NET Core 2.2. I expect this to be available in the first preview of ASP.NET Core 2.2 (perhaps due by the end of the month). I have started building up some fairly extensive notes as I explore the code and I hope to put together some follow up posts demonstrating more concrete samples once the previews are formally available (and at which point the API surface is less likely to change). I may also produce a deeper dive into how this all functions internally as its quite interesting code to explore.

Running a .NET Core Generic Host App as a Windows Service

I received a question at the weekend via a comment from Vadim on my Implementing IHostedService in ASP.NET Core 2.0 post. Vadim asked the following:

We have a lot of console applications that are listening to a queue and performing some work. In .net 4.6 we would make some of these classes inherit from ServiceBase class. Then we would use the sc.exe tool on the server to install these as Windows Services.

We are looking for a similar solution using .net core. The problem is, ServiceBase does not exist in .net core.

It seems like IHostedService is a good alternative. However, the one thing I don’t understand is, how do we take a .net core application with one or more IHostedServices and install them as a windows service? We want to be able to start/stop these applications from some kind of UI and we want them to always be running when the server is up.

This piqued my interest. I could vaguely recall reading something which described the process required to run an ASP.NET Core application as a Windows service. Off I went to Google and found the documentation which I’d previously read. The documentation was focused on running an ASP.NET Core app, hosted on Windows, as a Windows service. This looked like a good starting point.

I created a basic .NET Console application, using the Generic Host pattern and took a look to see if the RunAsService extension, mentioned in the ASP.NET Core documentation, would work. Turns out, it doesn’t, as it’s not available on the IHostBuilder interface, only for the IWebHostBuilder. Next, I thought I’d take a look at the ASP.NET Core Hosting repository to see if I could figure out if this was available under a different name or failing that to see if there were any plans for similar functionality for IHostBuilder. I came across this issue where Chris Ross (aka Tracher) linked to his GenericHostSample application.

Armed with that sample code I set about adding the relevant classes to my own app. It was mostly a copy/paste exercise; so full credit goes to Chris Ross for his sample code.

My next steps were then based on the ASP.NET Core documentation. However, as that document is specific to ASP.NET Core, I thought it would be valuable to summarise the steps required for running a generic host .NET core app as a Windows service here on my blog. I won’t cover everything in as much detail as the full documentation which I suggest you also go and read for additional context and explanation.

NOTE: A complete sample for a basic generic host based .NET Core Windows service can be found on my GitHub repository.

Project File

After creating a new .NET Core console application, the first thing we need to do is to add a reference to the Microsoft.Extensions.Hosting package and the Microsoft.AspNetCore.Hosting.WindowsServices System.ServiceProcess.ServiceController package (thanks to David Fowler for a PR to tidy up my dependencies in the sample).

As well as these package reference, you’ll see that in the main PropertyGroup section we’ve also set the RuntimeIdentifier to win7-x64. The RID (Runtime Identifier) in this case identifies that the target platform for this application is Windows. For more information about RIDs see the documentation. Since Windows Services are only available on Windows, it makes sense to set a windows target platform which will also ensure we produce a .EXE rather than .DLL file when building our application.

In the above sample we can also set the LangVersion to 7.1 to allow me to use an async Main method.

Program.cs

Next we need to setup our application entry point in Program.cs. The Main method is our entry point and in my sample is as follows:

I’ve based this example on the “Host ASP.NET Core in a Windows Service” documentation.

First it performs a check to see if we’re either debugging or if the application has been started having been passed an argument of “–console”. In that case we set the isService flag as false. We’ll use this later to allow us to debug the application from Visual Studio as a standard console application.

Next we create a HostBuilder which we’ll use to create the generic host. In here we register one service which is an implementation of the IHostedService interface. This is how we’ll run code within our application. See my earlier IHostedService post for more detail on how this interface can be used to run background workloads. We’ll look at the implementation for this application a little further down.

Finally, using the isService flag, we now call either the RunAsServiceAsync extension on the builder, or the RunConsoleAsync. The later will run the application as a normal .NET Core console app, perfect for local testing and debugging. The former is the extension method I was missing earlier. This comes from two files I have added, copied from the GenericHostSample mentioned earlier.

ServiceBaseLifetime.cs

I won’t go into the internals of this too deeply. This is entirely copied from the Microsoft sample. This class derives from the ServiceBase class used to define Windows services. It also implements the IHostLifetime interface from Microsoft.Extensions.Hosting.

ServiceBaseLifetimeHostExtensions.cs

This class is also copied from the GenericHostSample and adds the RunAsServiceAsync extension method which will ensure the ServiceBaseLifetime is registered as the implementation for IHostLifetime.

FileWriterService

This class implements IHostedService and once registered will be started and stopped by the IHost. We follow the generic host documentation and include a Timer which will fire every minute. It will run the code in WriteTimeToFile method which appends the current time to a file. Not particularly useful, but for this example it’s enough to show that something is actually happening. In the original question from Vadim the requirement was to poll a queue and process messages. We could just as easily perform that work here too.

At this point we now have our complete sample application. We can run this directly in Visual Studio to test it, but our main goal is to register this as a Windows service.

Again, if you want to download the source yourself you can do so from GitHub.

Creating the Windows Service

The final step is to build our code and register a service from the executable. We’ll follow the ASP.NET Core documentation to build our code and register a Windows service. Again, I won’t cover the detail here since the referenced documentation provides ample explanation. To summarise though, the steps are as follows:

Build and publish the project from the command line. This command can be run in the directory in which the project resides…

dotnet publish --configuration Release

Next, use sc.exe to create a service, passing the full path of the built executable…

sc create MyFileService binPath= "E:\Projects\IHostedServiceAsAService\IHostedServiceAsAService\bin\Release\netcoreapp2.1\win7-x64\publish\IHostedServiceAsAService.exe"

Next, use sc.exe to start the service (this needs to occur in a command prompt running as Administrator)…

sc start MyService

At this point, our service is running. In my case, I confirmed that a text file called TestApplication.txt appeared in my D: drive (that destination and filename is hardcoded in my sample), which confirms that the service is running as expected. Every minute, while the service is running, I can see a new line added to the text file.

Summary

If you’ve followed along, you should now have a .NET Core based Windows service running on your machine. As per the requirement in the original question, you could easily modify this service to read from a queue and process the messages it receives. Personally, I tend to deploy such services as a basic console application, running in Linux based Docker containers. For production workloads, this enables easy scaling and I can manage the services through our container orchestration in AWS ECS. However, should you need a Windows service, then hopefully this post is enough to get you started.

Hopefully, in a future release of ASP.NET Core, this functionality will be included in the framework. If that happens we won’t need to include our own ServiceBaseLifetime and ServiceBaseLifetimeHostExtensions classes. Based on a reply from Chris in the original GitHub issue, that looks like something which may be considered in the 3.0 timeframe as part of some wider hosting work they plan to do.