Intro
At uShip, our monolithic web application and the services supporting it are written in ASP.NET MVC, Web API, and hosted on IIS . We have begun the journey of transitioning to microservices. One of the important decisions we had to make early on was the choice of hosting model for our microservice APIs. We opted for a self-hosted solution, where we didn’t have to depend on the existence of IIS on a machine, instead allowing the application to create an instance of an HTTP server within its own process. Self-hosting also opens up the possibility for future Linux deployments. We invested a bit of time investigating OWIN and the new ASP.NET Core offering. Here’s what we found.
OWIN vs ASP.NET Core MVC 1.0
OWIN
OWIN is a specification that describes how web servers and web applications interact that has been around for quite a while. One can build applications on top of OWIN which removes the dependency on IIS, allowing self-hosting. Once the application is self-hosted, wrapping it in a Windows service allows Windows to help manage the application’s lifecycle.
ASP.NET Core MVC 1.0
ASP.NET Core MVC 1.0 (the framework formally known as ASP.NET 5 MVC 6) is a new web framework by Microsoft. It is not a successor to ASP.NET Web API 2.2 or MVC 5, the web frameworks built for .NET Framework 4.6 (the latest version of the full .NET Framework). Rather, it is an alternative web framework that one can use if their code can run on .NET Core, a re-imaging of the .NET Framework that includes a subset of the full .NET Framework and is cross platform. Web applications built with ASP.NET Core can be run on Kestrel, an HTTP server built for ASP.NET Core that will allow you to self-host your application.
The below are some of our reasons we chose self-hosting with OWIN instead of with Kestrel and ASP.NET Core:
- Stability. The ASP.NET Core ecosystem as a whole is not yet stable. At time of writing ASP.NET Core is on RC1, but we have seen code interface changes, project file changes, major tooling changes, and so on. The bleeding edge is too bloody for us to be productive in a codebase our size.
- Seamless upgrade. .NET Framework 4.6 is the natural upgrade path for us. Our software is more than 10 years old, and we have invested lots of time in learning various libraries, and have significant use of them throughout the entire codebase. A lot of these libraries (e.g,. NHibernate) are not yet compatible ASP.NET Core.
- Maturity. Nothing beats software that has stood the test of time in production. Along with reliability, there is also a plethora of online documentation for OWIN and tutorials by people that have already run into the problems that we will inevitably run into.
The below will help you implement your own OWIN self-hosted application. While the implementation is OWIN-specific, the overall idea of self-hosting is very similar and will be much easier to port to ASP.NET Core than an IIS deployment would be.
Terms To Be Familiar With
Below are some terms that you should be familiar with before moving on to the implementation
- OWIN: Open Web Interface for .NET. Acts as an adapter between web servers and web applications. This is a specification, not an implementation.
- Middleware: “plugins” for OWIN. Similar to an IHttpModule in IIS. If using Web API, these middlewares run before and after the ASP.NET pipeline finishes.
- Katana: Microsoft’s implementation of OWIN, a collection of NuGet packages for developing OWIN applications. A breakdown of the NuGet packages and their purposes are shown in the following diagram.
- Topshelf: An opinionated framework for easily developing Windows services.
Self-hosted Web API Hello, World! Windows service with OWIN and Topshelf
Note: All code is available on GitHub.
- In Visual Studio, create a new Console Application project called “OwinHelloWorld”
- Install the Microsoft.AspNet.WebApi.OwinSelfHost NuGet package
- Install the Topshelf NuGet package
- Add the following code:
using Microsoft.Owin.Hosting; using Owin; using System; using System.Web.Http; using Topshelf; namespace OwinHelloWorld { public class Program { public static int Main(string[] args) { return (int) HostFactory.Run(x => { x.Service<OwinService>(s => { s.ConstructUsing(() => new OwinService()); s.WhenStarted(service => service.Start()); s.WhenStopped(service => service.Stop()); }); }); } } public class OwinService { private IDisposable _webApp; public void Start() { _webApp = WebApp.Start<StartOwin>("http://localhost:9000"); } public void Stop() { _webApp.Dispose(); } } public class StartOwin { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); appBuilder.UseWebApi(config); } } public class HelloWorldController : ApiController { public string Get() { return "Hello, World!"; } } }
- Run the application one of the following ways:
- Hit F5 in Visual Studio to debug
- Run the exe to run the application as a regular process. The exe is usually located in SolutionRoot/SelfHostDemo/bin/Debug/SelfHostDemo.exe
- Manage the application as a Windows service
# Install and start the Windows service SelfHostDemo.exe install start # Stop and uninstall the Windows service SelfHostDemo.exe stop uninstall
- Hit http://localhost:9000/api/helloworld in your browser
Gotchas Encountered While Switching from IIS to a Self-hosted Model
It would be a bad to assume that you can simply port over your IIS-based codebase into a self-hosted model. The below are some gotchas that you may run into.
-
- HttpContext.Current: This will be null. HttpContext is IIS based and will not be set when self-hosting with OWIN.. If you have any code that relies on HttpContext, HttpRequest, or HttpResponse, it will have to be rewritten to handle an HttpRequestMessage or HttpResponseMessage, the HTTP types provided by Web API. Fortunately, we still have access to CallContext provided by ASP.NET. This class can be used to provide per-request static semantics. We have written an OWIN Middleware that gives us the request scope behavior of HttpContext.Current using CallContext:
using Microsoft.Owin; using System.Runtime.Remoting.Messaging; using System.Threading.Tasks; namespace OwinHelloWorld { /// <summary> /// Sets the current <see cref="IOwinContext"/> for later access via <see cref="OwinCallContext.Current"/>. /// Inspiration: https://github.com/neuecc/OwinRequestScopeContext /// </summary> public class OwinContextMiddleware : OwinMiddleware { public OwinContextMiddleware(OwinMiddleware next) : base(next) { } public override async Task Invoke(IOwinContext context) { try { OwinCallContext.Set(context); await Next.Invoke(context); } finally { OwinCallContext.Remove(context); } } } /// <summary> /// Helper class for setting and accessing the current <see cref="IOwinContext"/> /// </summary> public class OwinCallContext { private const string OwinContextKey = "owin.IOwinContext"; public static IOwinContext Current { get { return (IOwinContext) CallContext.LogicalGetData(OwinContextKey); } } public static void Set(IOwinContext context) { CallContext.LogicalSetData(OwinContextKey, context); } public static void Remove(IOwinContext context) { CallContext.FreeNamedDataSlot(OwinContextKey); } } }
- HttpRequestMessage.Content.ReadAsStreamAsync().Result: IIS let’s you read the request stream multiple times, but by default OWIN does not, not does it let you reset the stream after reading it once. A common reason people need to read the stream twice is to log the incoming request before the input body is deserialized by the framework. We have written an OWIN Middleware that copies the request stream into an in-memory buffer to get around this:
using Microsoft.Owin; using System.IO; using System.Threading.Tasks; namespace OwinHelloWorld { /// <summary> /// Buffers the request stream to allow for reading multiple times. /// The Katana (OWIN implementation) implementation of request streams /// is different than that of IIS. /// </summary> public class RequestBufferingMiddleware : OwinMiddleware { public RequestBufferingMiddleware(OwinMiddleware next) : base(next) { } // Explanation of why this is necessary: http://stackoverflow.com/a/25607448/4780595 // Implementation inspiration: http://stackoverflow.com/a/26216511/4780595 public override Task Invoke(IOwinContext context) { var requestStream = context.Request.Body; var requestMemoryBuffer = new MemoryStream(); requestStream.CopyTo(requestMemoryBuffer); requestMemoryBuffer.Seek(0, SeekOrigin.Begin); context.Request.Body = requestMemoryBuffer; return Next.Invoke(context); } } }
- IIS-specific modules: When investigating the switch from IIS to self-hosted, we discovered we relied on ISAPI_Rewrite, an IIS module that rewrites URLs ala Apache’s .htaccess. If we wanted to keep such behavior, we would need to either write an OWIN middleware that does the same thing, or somehow get a reverse proxy to do it.
- HttpContext.Current: This will be null. HttpContext is IIS based and will not be set when self-hosting with OWIN.. If you have any code that relies on HttpContext, HttpRequest, or HttpResponse, it will have to be rewritten to handle an HttpRequestMessage or HttpResponseMessage, the HTTP types provided by Web API. Fortunately, we still have access to CallContext provided by ASP.NET. This class can be used to provide per-request static semantics. We have written an OWIN Middleware that gives us the request scope behavior of HttpContext.Current using CallContext:
What the future holds
Once the ASP.NET Core MVC 1.0 ecosystem stabilizes, it may be a suitable alternative to building OWIN applications for most. But for now, writing a self-hosted application using OWIN might be the best choice for a pre-existing codebase. If you are one of the lucky few who is in a greenfield project, it is definitely worth building your application with Core in mind for an easy upgrade. For help on determining if your software can be ported to Core, see ApiPort.
[amp-cta id=’8510′]