In this blog post, I am going to walk through my experience using ASP.NET 5 (on my Mac!) to create a very simple microservice. I thought I would embrace the new separated, component nature of .NET 5 (the framework formerly known as vNext) and so I tried to use the Routing middleware directly.
I don’t know whether the original authors of the routing middleware intended it to be used directly, but it is an unwieldy beast that absolutely begs to have another layer of abstraction placed on top of it. In fact, it’s so difficult to use that I got about 30 lines into a static extension when I realized I could just use the ASP.NET Web API, which is actually just ASP.NET MVC 6. Confused yet? In classic Microsoft fashion, none of this stuff makes sense until you sit through a codename and versioning seminar.
Cliff notes: ASP.NET Web API 2 (which was in ASP.NET 4.5) is now rolled into ASP.NET MVC 6, which is a part of .NET 5. Clear as mud? Ok, let’s move on.
After installing ASP.NET 5 by going to http://get.asp.net, I made sure that everything worked by running dnvm list to make sure that I was pointing at the coreclr version of .NET. I installed the Yeoman tool to aid in scaffolding, but to be honest, I wanted to avoid generators as much as possible. If this is the new, open, unfettered Microsoft, then I should be able to create my project files with vi and use nothing but command-line tools, right?
When you create a new project (either by hand, if you’re a glutton for punishment, or using a yo generator), you get a stock project.json file. Here’s what mine looks like after running yo aspnet to create an empty ASP.NET Web API project (I’m importing the ASP.NET MVC libraries because, remember using the Routing middleware directly is an experience chock full o’ suck):
{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, "tooling": { "defaultNamespace": "SampleMicroservice" }, "dependencies": { "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", "Microsoft.Extensions.Configuration.FileProviderExtensions" : "1.0.0-rc1-final", "Microsoft.Extensions.Logging": "1.0.0-rc1-final", "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final", "Microsoft.Extensions.Logging.Debug" : "1.0.0-rc1-final" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules", "bower_components" ], "publishExclude": [ "**.user", "**.vspscc" ] }
This is a far cry from the good old days of ridiculous .csproj files and .sln files. Also, I should re-iterate that I’m doing this on my Mac. For those of us who have been using C# since before the 1.0 days, the concept of non-mono, Mac-compiled C# feels a little bit like washing ashore on paradise after being adrift for 15 years (I feel so old… I can remember writing C# in notepad and compiling with csc.exe before Visual Studio even existed).
If you’ve been following along with ASP.NET 5, you know that you now have a Startup.cs file that rigs everything up, and configures your application:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SampleMicroservice { public class Startup { public Startup(IHostingEnvironment env) { // Set up configuration sources. var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; set; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseIISPlatformHandler(); app.UseStaticFiles(); app.UseMvc(); } // Entry point for the application. public static void Main(string[] args) => Microsoft.AspNet.Hosting.WebApplication.Run<Startup>(args); } }
This is also mostly boilerplate. The highlights here are “services.AddMvc()” which, using dependency injection and a pretty robust module architecture, sets your application up for ASP.NET MVC routing and template rendering.
Now that we have that ceremony and configuration out of the way, let’s create a controller (there’s no longer a distinction between an MVC controller and a Web API controller, and that’s a good thing) that serves up the usual array of Zombies:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc; namespace SampleMicroservice.Controllers { public class Zombie { public String Name { get; set; } public int Age { get; set; } } [Route("api/zombies")] public class ZombieController : Controller { [HttpGet] public IEnumerable<Zombie> Get() { return new Zombie[] { new Zombie() { Name = "Bob", Age = 21}, new Zombie() { Name = "Alfred", Age = 52} }; } } }
With all this in place, we can do dnu restore (you might need to do this in a sudo. RC1 creates files with silly permissions, at least on my Mac). This will vendor our dependencies based on the project.json file. Next, we can do dnx web (also might need to sudo this as well).
If everything worked properly, I can hit http://localhost:5000/api/zombies and get an array of properly JSON-serialized objects. I won’t go into the details of creating the POST/PUT/DELETE methods on the controller because that hasn’t changed much.
In conclusion, I am truly excited about the prospect of being able to build microservices and web applications in ASP.NET 5. I am even more excited about the fact that I might be able to do this using nothing but my Mac and my favorite non-Microsoft text editor (vi FTW baby!). I was a little disappointed that tapping directly into the Routing middleware was cumbersome and ugly, but slotting in ASP.NET MVC 6 as a service worked like a champ and gave me an annotation-based routing configuration that works just like the old web API.
I am looking forward to exploring ASP.NET 5 further!