This article mostly focuses on OWIN and Katana which is an implementation of OWIN. The inner working of OWIN Katana is explained in detail. A short chapter is allocated to discuss the Web API development with OWIN Katana, followed by a brief introduction to the ASP.NET Core at the end.
[Note: You can use the downloaded projects as a template to start OWIN Katana or WebAPI applications.]
Topic Outline
Introduction
I was asked to give a presentation about OWIN and Web API to a different development team, as they wanted an introduction to the technology and no one in the team had exposure to that technology before. Prior to that, I have developed a number of internal API for our company. With the presentation slides and materials, I decided to write them down as a blog post as well, so it will be documented properly. I put a lot of extra information compared to the original presentation to cover a broader audience in the internet.
Even with recent revolutionary changes in the ASP.NET Core, I found out that understanding OWIN is fundamental to understanding the framework and features of the ASP.NET Core. There are many concepts, and architectural designs in OWIN which still apply to ASP.NET Core. This post mostly focuses on OWIN and Katana which is an implementation of OWIN. The inner working of OWIN Katana is explained in detail. After that, a short chapter is allocated to discuss the Web API development with OWIN Katana, followed by a brief introduction to the ASP.NET Core at the end.
Part 1 - What is OWIN
OWIN stands for Open Web Interface for .NET. OWIN is a open standard specification which defines a standard interface between .NET web servers and web applications. The aim is to provide a standard interface which is simple, pluggable and lightweight.
OWIN is motivated by the development of web frameworks in other coding languages such Node.js for JavaScript, Rack for Ruby, and WSGI for Phyton. All these web frameworks are designed to be fast, simple and they enable the development of web applications in a modular way. In contrast, prior to OWIN, every .NET web application required a dependency on >System.Web.dll, which tightly coupled with Microsoft's IIS (Internet Information Services). This meant that .NET web applications came with a number of application component stacks from IIS, whether they were actually required or not. This made .NET web applications, as a whole, heavier and they performed slower than their counterparts in other coding languages in many benchmarks
OWIN was initiated by members of Microsoft's communities; such as C#, F# and dynamic programming communities. Thus, the specification is largely influenced by the programming paradigm of those communities. From initial prototyping, OWIN was then reduced to just a common specification. It basically specifies, as shown in Figure 1:
- Type of object to be passed around, between web servers and web applications, and between web application components. This is also called the environment object.
- The structure of web application components. The structure should be simple and is represented in a consistent way.
Other constraints include having no dependency, except for Foundation Class Language (FCL) types and the need to support async processing.
|
Figure 1. Scope of OWIN |
|
Figure 2. OWIN Specifications (Interaction between web server and web application) |
Figure 2 shows OWIN overview of web servers and web applications interaction, which contains at least 4 important aspects:
- OWIN introduces a key value pair dictionary,
Dictionary<string, object>
, as the environment object to be passed between web servers. and web applications, and in-between web application components. OWIN also specifies the following keys:
Required | Key | Type (Default) |
yes | owin.RequestBody | Stream |
yes | owin.RequestHeaders | IDictionary<string, string[]> |
yes | owin.RequestMethod | string |
yes | owin.RequestPath | string |
yes | owin.RequestPathBase | string |
yes | owin.RequestProtocol | string |
yes | owin.RequestQueryString | string |
yes | owin.RequestScheme | string |
yes | owin.ResponseBody | Stream |
yes | owin.ResponseHeaders | IDictionary<string, string[]> |
no | owin.ResponseStatusCode | int(200) |
no | owin.ResponseReasonPhrase | string (OK) |
no | owin.ResponseProtocol | string (HTTP/1.1) |
yes | owin.CallCancelled | CancellationToken |
yes | owin.Version | string |
(Source: http://owin.org)
All these key values are not nullable, and should only be compared by their ordinal code (case sensitive). Web servers are responsible for constructing this environment object on receipt of a http request, and pass it to a web application.
- Each web application component is modelled as a delegate function,
Func<IDictionary<string, object>,Task>
which means the component accepts an environment object IDictionary<string, object>
, and returns an awaitable Task
. This also means that the delegate supports asynchronous calls. The delegate function is given a name AppFunc
, and web application components in OWIN are called middleware. - A pipeline model is used to structure middleware. Unfortunately, OWIN 1.0 does not specify how middleware should be chained into a pipeline. A later draft attempted, targeting OWIN 1.0, to include specification for middleware and pipeline builder. It is now an expired working in progress draft since 1 March 2016 and seems to be abandon. Even though it is not specified, there will be always a need for a pipeline/middleware/application builder in a real implementation.
So what is the requirement for the builder to be able to register middleware? The pipeline structure implicates that the implementation of a middleware can optionally call other or 'next' middleware. For example, the first middleware calls the next middleware and so on, until the last one which terminates or returns the call directly (The first middleware is normally called a pass-through middleware, and the last one is named a terminating middleware). In order to be able to chain middleware into a pipeline as a 'delegate' function (delegate is a first class citizen in OWIN, mostly influenced by functional programming paradigm), the builder needs a delegate with the signature of Func<AppFunc, AppFunc>
. The first, or the argument, AppFunc
is the placeholder for the 'next
' middleware, while the second one, or the return, is the middleware body. The middleware body calls the 'next
' middleware if it is a pass-through middleware.
When the builder builds the pipeline, it executes the registered delegate functions in a reversed order, one after another and passes the result of the last execution (a middleware body) as the argument of the next execution ('next
' argument of the executed delegate function), until the first middleware body (AppFunc
) is retrieved and acts as an entry point of the pipeline. This is an elegant solution for a functional programmer (F#), but for an object-oriented programmer (C#), this may cause a confusion, as this approach is not a norm and is not aligned with an object-oriented thinking even though the C# language itself supports delegate. This issue will be discussed later when talking about creating middleware using Katana, which is an implementation of OWIN in C#.
OWIN makes a clear distinction between host, server, middleware, web framework, and web application. However, OWIN specification only deals with host, server and part of the middleware. Below is the explanation taken from OWIN website.
Server
— The HTTP server that directly communicates with the client and then uses OWIN semantics to process requests. Servers may require an adapter layer that converts to OWIN semantics. Web Framework
— A self-contained component on top of OWIN exposing its own object model or API that applications may use to facilitate request processing. Web Frameworks may require an adapter layer that converts from OWIN semantics. Web Application
— A specific application, possibly built on top of a Web Framework, which is run using OWIN compatible Servers. Middleware
— Pass through components that form a pipeline between a server and application to inspect, route, or modify request and response messages for a specific purpose. Host
— The process an application and server execute inside of, primarily responsible for application startup. Some Servers are also Hosts.
(Source: http://owin.org)
Even though it is possible to write a whole ground up web application using the middleware pipeline, that structure is, in fact, intended as an infrastructure for web development. Middleware is particularly suitable for writing web frameworks and cross cutting aspects of web applications such as error handling, logging, etc., and web applications can be written based on the web framework registered in the pipeline. Using the structure also encourages developers to split out cross cutting aspects from web applications.
- Web applications or web frameworks or middleware written for an OWIN implementation do not have to rely on any IIS features, and they can be plugged with any web servers which understand OWIN semantics and be hosted in different ways. OWIN compatible servers are required to implement rules specific to the server specification. Consequently, middleware can make assumptions about the availability of mandatory fields in the environment object. Middleware also need to follow rules for error or exception handling, and the order of writing response body and response headers. It is worth reading the whole OWIN specification and it does not take a long time.
The specification is just a beginning. The other goal is to encourage the development of simple and re-usable modules for .NET. The expectation is to have a signification growth of middleware that can be downloaded as nuget packages. The long-term goal is to stimulate open source ecosystem of .NET web development.
Part 2 - What is Katana
OWIN has a number of implementations such as Katana for the C# community, and Freya for the F# community. There are also a number of libraries that only implement host and server OWIN specifications such as Nowin
and Suave
. These lists are in the OWIN website.
Katana is the Microsoft's implementation of OWIN specification. They are in the form of nuget packages, which all have a namespace Microsoft.Owin
. However, Katana is more than just an implementation of OWIN as it also has other abstractions to help the productivity in development. Katana adds a number of object-oriented constructs such as the IOwinContext
interface and the OwinMiddleware
abstract class. The IOwinContext
is used as an abstraction for the environment object and can be instantiated as an OwinContext
by taking a IDictionary<string, object>
object as the argument in the constructor, while the OwinMiddleware
serves as a base class for creating a middleware class. In fact, OWIN has not specified the standard form of middleware or the pipeline builder. However, from the look in the OWIN Middleware draft, only delegates are used. In contrast, being an object-oriented C# implementation of OWIN, Katana uses the AppBuilder
class, which implements the interface IAppBuilder
, as the pipeline builder. In addition to that, Katana allows creating middleware as a class, which can be registered using the class type or class instance by the AppBuilder
. It should be assumed that these Katana's middleware classes can only be used by the AppBuilder
.
Katana also has a number of constructs to help accessing the environment object such as the IOwinRequest
and the IOwinResponse
, which are implemented by the OwinRequest
and OwinResponse
classes respectively. OWIN compatible middleware can still be authored by limited only using Katana's constructs such as OwinContext
, OwinRequest
, OwinResponse
inside the middleware body.
Moreover, Katana also contains hosting components and a listener component, Hosting components deal with the server aspects of applications such as creating listeners, or converting a received http request to an environment object complying with OWIN semantics, and passing the environment object into the middleware pipeline. When creating an OWIN application with the host together or self hosting such as creating an OWIN application running as a console application, winforms application, or windows service, both hosting and listener components are required. On the other hand, if the OWIN application is hosted with IIS, only the hosting component is required to convert a http request to an environment object and passing it into the middleware pipeline. Katana also implements a host called 'OwinHost
', as a nuget package, which can be used to replace IIS. The differences between the two is that IIS has its own pipeline while 'OwinHost
' does not have one. When hosted with IIS, the server or listener is in the IIS infrastructure, and the OWIN application is operated under an IIS Integrated pipeline model, where the IIS pipeline is combined with OWIN middleware pipeline, and this will be explained in the next part. All above hosting components are split into a number of different packages, so they can be used selectively to fit the application.
Lastly, Katana has a number of common middleware such as CORS, Diagnostic, Static Files, Security and helper and utility classes. Below is the important assemblies or DLLs related to Katana:
Microsoft.Owin
- Katana implementation of OWIN Microsoft.Owin.Host
- Common library for hosting component Microsoft.Owin.Host.HttpListener
- Listener or server implementation Microsoft.Owin.Host.SystemWeb
- Hosting component for IIS Hosting Microsoft.Owin.Diagnostic
- Diagnostic middleware Microsoft.Owin.Cors
- Cors middleware Microsoft.Owin.StaticFiles
- Static files middleware Microsoft.Owin.Security
- Common library for security and Identity Microsoft.Owin.Security.*
- Middleware for specific security/identity model, e.g., Cookies, active directory, Facebook, Google, jwt, oauth, wsfederation, twitter, Microsoft Account, open ID connect Owin
- OWIN abstraction OwinHost
- Katana host implementation
These assemblies are distributed via a number of nuget packages, e.g., OwinHost
, Microsoft.Owin.Host.SystemWeb
, and Microsoft.Owin.Host.SelfHost
. Katana implementation is open source, and the source code can be accessed from https://github.com/aspnet/AspNetKatana.
Part 3 - Application Development with OWIN Katana
As mentioned in previous part, OWIN Katana provides a structure to write web applications. Cross cutting concerns, such as logging and exception handling, and web frameworks, such as Web API, can be written as middleware. The main web application logic can be written using the web framework middleware registered in the pipeline. In this part, OWIN Katana applications development is attributed to the activity of writing middleware, creating a middleware pipeline and starting listeners or servers. Developing web applications using Web API framework will be discussed in the next part separately.
3.1. Creating Listeners or Servers
Creating listeners or servers is only required for self hosting scenarios and not a requirement for OwinHost or IIS hosting models. Katana provides a static
method WebApp.Start
from Microsfot.Owin.Hosting
to create a server or servers. Creating servers are the same as creating listeners and activating them. There are a number of overload methods and generic methods to do this.
IDisposable Start(string url, Action<IAppBuilder> startup)
IDisposable Start(StartOptions startOptions, Action<IAppBuilder> startup)
IDisposable Start<TStartup>(string url)
IDisposable Start<TStartup>(StartOptions startOptions)
IDisposable Start(StartOptions startOptions)
IDisposable Start(string url)
There are two important pieces of information required when creating a listener in OWIN Katana, the listening url
and the startup
delegate, as shown in the first overload. The listening url
is the endpoint to receive http requests, while the startup
delegate, which has a signature Action<IAppBuilder>
, is the function to configure a middleware pipeline.
The WebApp.Start
method not only creates a listener but also joins it to a middleware pipeline. This method creates a middleware pipeline builder, of type IAppBuilder
, passes to the startup
delegate, to be configured, and builds the middleware pipeline to get an AppFunc
entry to first middleware in the pipeline. The startup
delegate can take a number of forms: method name (method groups), delegate variable, or lambda expression, as demonstrated in the code below:
public class Program
{
public static void Main(string[] args)
{
using (var host = WebApp.Start("http://localhost:9000", CustomConfiguration))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
Action<IAppBuilder> startup = CustomConfiguration;
using (var host = WebApp.Start("http://localhost:9000", startup))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
using (var host = WebApp.Start("http://localhost:9000",
builder => builder.Use(typeof(Middleware))))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
When running a server application, the url
and particularly the port must not be used by other applications, and the user account running the application must have a permission to open the port. If this happens, the application throws an access denied exception. Run the following command prompt with an administrative privilege.
netsh http>add urlacl http://[ipaddress]:[port]/ user=Everyone
Using the second overload method, multiple listening urls
can be created by setting the StartOptions
's Urls
property. The example code below demonstrates how to create a server which listens at multiple urls
.
public static void Main(string[] args)
{
StartOptions startOptions = new StartOptions();
startOptions.Urls.Add("http://localhost:9000");
startOptions.Urls.Add("http://localhost:9500");
startOptions.Urls.Add("http://localhost:9700");
using (var host = WebApp.Start(startOptions, CustomConfiguration))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
The third and fourth generic methods is similar to first and second methods, except they accept a generic startup
class instead of a startup
delegate. In order to be used as a startup
class for these methods, the class must not be static
and has an instance or static
method with the name Configuration
. This method must have the same signature as the startup
delegate, Action<IAppBuilder>
. The code below shows an example of creating a server using a startup
class.
public class Program
{
public static void Main(string[] args)
{
using (var host = WebApp.Start<CustomStartup>("http://localhost:9000"))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
}
public class CustomStartup
{
public static void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
The fifth overload method only accepts one argument, StartupOption
. The startup
class or delegate can be set in the StartupOption
's AppStartup
property. The property can take a 'static
' or instance startup
class name. It can also take any method name with a signature of Action<IAppBuilder>
. The example code below illustrates the usage of the fifth overload method.
public class Program
{
public static void Main(string[] args)
{
StartOptions startOptions = new StartOptions();
startOptions.Urls.Add("http://localhost:9000");
startOptions.Urls.Add("http://localhost:9500");
startOptions.AppStartup = "Owin.Application.CustomStartup";
startOptions.AppStartup = "Owin.Application.CustomStartup.CustomConfiguration";
using (var host = WebApp.Start(startOptions))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
}
public static class CustomStartup
{
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(...);
}
}
If AppStartup
property has not been set, the method will auto detect a startup
class, which is similar to what the six or final overload method, which does not accept a startup
delegate or a startup
class, does. The following list are the rules for auto detect a startup
class:
Naming convention
- Katana will look for a static
or instance class with the name Startup
(case sensitive), and contains a static
or instance method with the name Configuration
in the root (global) namespace or the namespace which matches with the assembly name. OwinStartupAttribute
- This method takes precedence over the naming convention. Katana will look for this attribute specified as an assembly level attribute, e.g. [Assembly:OwinStartup(typeof(CustomStartup))]
. It can be declared anywhere allowed for assembly attributes. The attribute has three overload methods. The first one, like the example, takes the type of a startup
class. The second ones takes both the type of a startup
class and its startup
method. The third one, takes a friendly name and the type of a startup
class. The friendly name is to be used in conjunction with the configuration file. Configuration file
- This method takes precendence over the OwinStartupAttribute
method. Katana will look in the application configuration file for a key with the name owin:appStartup
under the appSettings
section. The key can be used to link to a startup
class directly by specifying the full name of the class. Alternatively, the key can be used to link to a OwinStartupAttribute
by specifying the friendly name of the attribute. Multiple OwinStartupAttribute
s, each with a different friendly name can be declared. Then, the configuration file can decide which one to be used in the owin:appStartup
key.
The application can also control the OWIN Startup class auto detection feature explicitly using another configuration key owin:AutomaticAppStartup
, by setting it to 'true
' or 'false
'. A detailed explanation about OWIN Startup
class detection can be found in this MSDN article.
3.2. Configuring Middleware Pipeline
The second element of creating an OWIN Katana application is to configure the middleware pipeline. OWIN Katana provides a builder abstraction, IAppBuilder
, to allow developers to configure the pipeline. The builder implementation AppBuilder
is created inside WebApp.Start
method and then it invokes the startup
method with the builder as the argument. Therefore, the function of the startup
method is to configure the pipeline.
The IAppBuilder
utilises a Use
method, which has the same signature as Action<object middleware, object[] args>
, to register middleware. The middleware object, which is a dynamic type, can take a number of forms, such as delegate, class type or class instance. Additionally, middleware can optionally have one or more arguments. The code below demonstrates the registration of a Logging
middleware class with a loggingOptions
argument:
public void Configuration(IAppBuilder appBuilder)
{
var loggingOptions = new LoggingOptions()
{
Level = 1
};
appBuilder
.Use(typeof(Logging), loggingOptions);
}
The Use
method usage will be discussed in a more detail in the next section about creating middleware.
Moreover, Katana provides a number of extension methods for the application builder, IAppBuilder
. Some important ones are:
Use<T>(string[] args)
Use(Func<IOwinContext, Func<Task> /*next*/, Task> handler)
Run(Func<IOwinContext, Task> handler)
Map(string pathMatch, Action<IAppBuilder> configuration)
MapWhen(Predicate predicate, Action<IAppBuilder> configuration)
MapWhenAsync(PredicateAsync predicate, Action<IAppBuilder> configuration)
The generic methods Use<T>
provides an alternative way to register middleware if the middleware is implemented as a class and not a delegate. It can optionally take one or more middleware arguments. Using this method, the previous code example can be written as below:
public void Configuration(IAppBuilder appBuilder)
{
var loggingOptions = new LoggingOptions()
{
Level = 1
};
appBuilder
.Use<Logging>(loggingOptions);
}
The second and third methods provide different ways to register middleware and they use Katana abstraction and not OWIN abstraction. Under the hook, the methods register the UseHandlerMiddleware
middleware, and they take a handler
as the middleware argument. A handler is equivalent to a middleware body. The second method takes a handler which accepts IOwinContext
and the 'next
' component (Func<Task>
), while the Run
method's handler only takes IOwinContext
. The third method should only be used for registering the last middleware, or the middleware handler to be precise, in the pipeline. Below is the example of the usage of the second and third methods:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(PassThroughHandler)
.Run(TerminatingHandler);
}
public async Task PassThroughHandler(IOwinContext context, Func<Task> next)
{
System.Console("This is an inbound flow of a pass through middleware");
await next.Invoke();
System.Console("This is outbound an flow of a pass through middleware");
}
public async Task TerminatingHandler(IOwinContext context)
{
System.Console("This is a terminating middleware");
}
The fourth or Map
method provides a branching strategy for the pipeline based on the path matching evaluation of the request's URI. Under the hook, it uses the MapMiddleware
middleware. This middleware checks if the request's URI Path
starts with the given pathMatch
argument. If it matches, the middleware executes the map pipeline, which is configured by the method's second argument delegate. The middleware becomes a terminating middleware for the main pipeline. Otherwise, the middleware continues the execution to the next middleware in the main pipeline.
When the middleware execution goes into the map pipeline, the pathMatch
is added to the request's URI BasePath
and is reduced from request's URI Path
. Moreover, when using the Map
method, the pathMatch
argument should not end with the '/
' character. Below is an example of the Map
method usage.
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Map("/Admin", ConfigurationForAdmin)
.Use(typeof(MainPipelineMiddleware));
}
public void ConfigurationForAdmin(IAppBuilder appbuilder)
{
appBuilder
.Use(typeof(AdminPipelineMiddleware));
}
The MapWhen
and MapWhenAsync
methods is similar to Map
method, except that their evaluation using a predicate delegate, an expression that return a Boolean, instead of using the path matching. The MapWhenAsync
in particular uses an awaitable predicate delegate. Under the hook, they use the MapWhenMiddleware
middleware. If the predicate delegate returns 'true
', the middleware executes the map pipeline, otherwise it continues the execution to the next middleware in the main pipeline. The predicate clause takes an IOwinContext
argument, which gives the flexibility to evaluate the whole request and not just the request's URI Path
. Below is an example of MapWhen
and MapWhenAsync
methods usage.
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.MapWhen(IsPostMethod, ConfigurationForPostMethod)
.MapWhenAsync(IsGetMethodAsync, ConfigurationForGetMethod)
.Use(typeof(MainPipelineMiddleware));
}
public bool IsPostMethod(IOwinContext context)
{
return context.Request.Method == "POST";
}
public async Task<bool> IsGetMethodAsync(IOwinContext context)
{
return await Task.FromResult(context.Request.Method == "GET");
}
public void ConfigurationForPostMethod(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(PostMethodPipelineMiddleware));
}
public void ConfigurationForGetMethod(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(GetMethodPipelineMiddleware));
}
When using multiple Map
or MapWhen
in the pipeline, more specific constraints should be put first, otherwise they will never have a chance to be evaluated.
3.3. Creating Middleware
Writing Middleware is part of the OWIN Katana applications development. The Use
method is the main mechanism to register middleware into the pipeline. The signature of the method is IAppBuilder Use(object middleware, params object[] args)
, which takes middleware object, and optional middleware arguments. All parameters are dynamic objects and the correct argument types must be provided otherwise the application will throw a runtime exception.
Middleware can be created as a delegate function or a class, and there are a number of ways to register them into the pipeline. Below are the several ways to create middleware based on the mechanism to register them into the pipeline.
3.3.1. Creating a Middleware Delegate, Registering by a Delegate
For a delegate to be qualified as a middleware object, the delegate must have a signature of Func<AppFunc, AppFunc>
. The first AppFunc
, or the delegate's argument type, represents the 'next
' middleware component, while the second AppFunc
, or the delegate's return type, is the middleware body. If the middleware also has additional arguments, known as the middleware options in the Katana terminology, the delegate signature must be Func<AppFuc, args, AppFunc>
. For example, if the middleware has string
and int
arguments, the delegate must be Func<AppFunc, string, int, AppFunc>
. The example code below illustrates how to create and register middleware into the pipeline as a delegate.
namespace MiddlewareUsingDelegate
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
Func<AppFunc, AppFunc> delegateMiddleware = new Func<AppFunc,
AppFunc>(MiddlewareMethod);
delegateMiddleware = MiddlewareMethod;
appBuilder
.Use(delegateMiddleware)
.Use((Func<AppFunc, AppFunc>)MiddlewareMethod)
.Use(new Func<AppFunc, AppFunc>((next =>
{
AppFunc middlewareMainComponent = async env =>
{
Console.WriteLine("Inbound with delegate");
await next.Invoke(env);
Console.WriteLine("Outbound with delegate");
};
return middlewareMainComponent;
})))
.Use((Func<AppFunc, string, AppFunc>)
MiddlewareMethodWithOptions, "Options");
}
public AppFunc MiddlewareMethod(AppFunc next)
{
AppFunc middlewareBody = async (env) =>
{
Console.WriteLine("Inbound with delegate");
await next.Invoke(env);
Console.WriteLine("Outbound with delegate");
};
return middlewareBody;
}
public AppFunc MiddlewareMethodWithOptions(AppFunc next, string options)
{
AppFunc middlewareBody = async (env) =>
{
Console.WriteLine(string.Format("Inbound with delegate {0}", options));
await next.Invoke(env);
Console.WriteLine(string.Format("Outbound with delegate {0}", options));
};
return middlewareBody;
}
}
}
The first three registrations basically do the same thing. The first one assigns the delegate into a variable first before passing it into the Use
method. The variable can be assigned from a delegate construction or a method groups assignment or a lambda expression (not shown). The method groups and lambda expression cannot be used directly in the Use
method. They need to be converted into a delegate first, either via a delegate construction or casting (2 & 3). The method 4 shows a registration of a middleware delegate with middleware options. The delegate, or middleware method, can be divided into two execution blocks. The first block is to be executed once when the pipeline is built. The second block, or the middleware body, is to be executed for every received http request.
As discussed in the previous section, Katana provides extension method Use
and Run
which can accept middleware handler delegates Func<IOwinContext, Func<Task>, Task>
and Func<IOwinContext, Task>
respectively. It is called handlers because the under the hook they already use the UseHandlerMiddleware
middleware. Using these methods, these handlers can be registered as method groups or a lambda expression directly in the Use
or Run
methods.
3.3.2. Creating a Middleware Class, Registering by a Class Type
For a class to be qualified as a middleware object and can be registered by its class type, the class must have a constructor which at least accepts an AppFunc
delegate representing 'next
' middleware component. The class can have additional arguments in the constructor, and when registering the middleware, the corresponding arguments must be passed as the args
parameter. Additionally, the class must have an Invoke
method with a signature of Func<Task, AppFunc>
. The example code below shows the registration of middleware using their class type, which can be done by using typeof
operator (1). Additionally, the args
parameter can be provided if the middleware has the associated constructor (2).
namespace MiddlewareUsingClassType
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(ClassTypeMiddleware))
.Use(typeof(ClassTypeMiddleware), "Options");
}
}
public class ClassTypeMiddleware
{
private AppFunc _next;
private string _option;
public ClassTypeMiddleware(AppFunc next)
{
_next = next;
}
public ClassTypeMiddleware(AppFunc next, string option)
{
_next = next;
_option = option;
}
public async Task Invoke(IDictionary<string, object> env)
{
Console.WriteLine(string.Format("Inbound with class type {0}", _option));
await _next.Invoke(env);
Console.WriteLine(string.Format("Outbound with class type {0}", _option));
}
}
}
Alternatively, Katana also provides its own abstraction to implement a middleware class, by deriving from the OwinMiddleware
abstract class, and can be registered into the pipeline by its class type. Its constructor takes an OwinMiddleware
instead of an AppFunc
, and the Invoke
method takes IOwinContext
instead of IDictionary<string, object>
. Below is an example code of creating and registering middleware using a Katana middleware class. Method 2 is an example with a middleware argument.
namespace MiddlewareUsingKatanaClassType
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(KatanaClassTypeMiddleware))
.Use(typeof(KatanaClassTypeMiddleware), "Options");
}
}
public class KatanaClassTypeMiddleware : OwinMiddleware
{
private string _options;
public KatanaClassTypeMiddleware(OwinMiddleware next) : base(next)
{
Next = next;
}
public KatanaClassTypeMiddleware(OwinMiddleware next, string options) : base(next)
{
Next = next;
_options = options;
}
public override async Task Invoke(IOwinContext context)
{
Console.WriteLine(string.Format("Inbound with Katana middleware {0}", _options));
await Next.Invoke(context);
Console.WriteLine(string.Format("Outbound with Katana middleware {0}", _options));
}
}
}
As discussed in the previous section, Katana provides a generic method Use<T>
as an alternative to using the typeof
operator. This method can be used for all middleware classes discussed in this section. The middleware registration in the previous example can be rewritten as follows:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use<KatanaClassTypeMiddleware>()
.Use<KatanaClassTypeMiddleware>("Options");
}
3.3.3. Creating a Middleware Class, Registering by a Class Instance
For a class to be qualified as a middleware object, and can be registered by its class instance, the corresponding class must either:
- have an
Initialize
method which accepts an AppFunc
, representing 'next
' middleware component, and optionally middleware arguments, and an Invoke
method with a signature of Func<IDictionary<string, object>, Task>
- have an
Invoke
method with a signature of Func<AppFunc, args, AppFunc>
, where args
are for middleware arguments
The example codes below illustrate middleware classes with an Initialize
method (1 & 2), and without an Initialize
method (3 & 4). The methods 2 & 4 demonstrate middleware classes with a middleware argument. For method 2, the class must have an Initialize
method with a signature of Action<AppFunc, args>
. For method 4, the class must have an Invoke
method with a signature of Function<AppFunc, args, AppFunc>
.
namespace MiddlewareUsingClassInstance
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(new ClassInstanceMiddlewareWithInitialize())
.Use(new ClassInstanceMiddlewareWithInitialize(), "Options")
.Use(new ClassInstanceMiddlewareNoInitialize())
.Use(new ClassInstanceMiddlewareNoInitialize(), "Options");
}
}
public class ClassInstanceMiddlewareWithInitialize
{
private AppFunc _next;
private string _options;
public void Initialize(AppFunc next)
{
_next = next;
}
public void Initialize(AppFunc next, string options)
{
_next = next;
_options = options;
}
public async Task Invoke(IDictionary<string, object> env)
{
Console.WriteLine(string.Format
("Inbound with class instance (with Initialize) {0}", _options));
await _next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbound with class instance
(with Initialize) {0}", _options));
}
}
public class ClassInstanceMiddlewareNoInitialize
{
public AppFunc Invoke(AppFunc next)
{
return async (env) =>
{
Console.WriteLine(string.Format("Inbound with class instance (No Initialize)"));
await next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbond with class instance (No Initialize)"));
};
}
public AppFunc Invoke(AppFunc next, string options)
{
return async (env) =>
{
Console.WriteLine(string.Format
("Inbound with class instance (No Initialize) {0}", options));
await next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbond with class instance
(No Initialize) {0}", options));
};
}
}
}
When creating middleware, it is a common pattern to create extension methods on IAppBuilder
for registering the middleware. The benefit is instead of using a raw Use(object middleware, params object[] args)
method, developers can use a more meaningful method UseXXX(param object[] args)
or UseXXX()
if the middleware does not have middleware arguments (substitute XXX with the middleware name, such as Logging, ExceptionHandling
, etc.)
3.4. IIS Integrated Pipeline
As mentioned in the previous part, Katana is not just an OWIN implementation. Even though the original purpose of OWIN is to remove the application dependency from IIS, Katana provides a mechanism to host web applications with IIS. All previous examples are written from the context of self hosting. So, what is the different if OWIN Katana applications are implemented and hosted with IIS? There are at least a number of differences:
- When hosting with IIS, the listeners and server applications are created and maintained by the IIS infrastructure. So there is no need to create listeners. However, to allow the listeners to convert http requests to OWIN semantics and auto-detect the OWIN
Startup
class, the OWIN library Microsoft.Owin.Host.SystemWeb
is required to be in the application bin
folder. This can be done in the project by referencing a nuget package Microsoft.Owin.Host.SystemWeb
- Because there is no place to define a
startup
method, the application will use auto detect OWIN Startup
class mechanism, which has been discussed in previous part. - If the application is hosted with an IIS server, e.g., not IIS Express, the application needs to run in an IIS Integrated application pool.
- When hosting with IIS, the OWIN Katana pipeline is integrated with the IIS pipeline, thus this model is also called the IIS integrated pipeline. OWIN Katana pipeline will remain the main entry point. The difference with self hosting is the OWIN Katana pipeline can be interleaved with the IIS pipeline. Below is all the stages of the IIS pipeline.
public enum PipelineStage
{
Authenticate = 0,
PostAuthenticate = 1,
Authorize = 2,
PostAuthorize = 3,
ResolveCache = 4,
PostResolveCache = 5,
MapHandler = 6,
PostMapHandler = 7,
AcquireState = 8,
PostAcquireState 9,
PreHandlerExecute = 10
}
UseStageMarker
method is used to tell all middleware before that marker need to be executed at the marker stage. By default, all OWIN Katana middleware is executed at the IIS PreHandlerExecute
stage, as if there is such UseStageMarker
at the end of the pipeline. For example, if the OWIN katana middleware pipeline is like below:
public void Configuration(IAppBuilder app)
{
app
.Use<MiddlewareOne>()
.UseStageMarker(PipelineStage.Authenticate);
.Use<MiddlewareTwo>()
.UseStageMarker(PipelineStage.PostMapHandler);
.Use<MiddlewareThree>()
.Run<MiddlewareFour>();
}
Above OWIN Katana middleware will be combined with the IIS pipeline and sorted as follows:
Authenticate
stage: MiddlwareOne
, IIS features PostAuthenticate
stage: IIS features Authorize
stage: IIS features PostAuthorize
stage: IIS features ResolveCache
stage: IIS features PostResolveCache
stage: IIS features MapHandler
stage: IIS features PostMapHandler
stage: MiddlewareTwo
, IIS features AcquireState
stage: IIS features PostAcquireState
stage: IIS features PreHandlerExecute
stage: MiddlewareThree
, MiddlewareFour
ExecuteRequestHandler
phase : IIS Features Only (No OWIN middleware here)
IIS features represent IIS middleware that can be configured through IIS or web configuration and may or may not be active at the time of execution. Because the IIS integrated pipeline model follows IIS stages in their execution, the stage marker should be placed in the correct order in the pipeline configuration. If some of the stage marker is out of order it wil be ignored and no exception will be thrown. A more detail explanation can be found in the following MSDN article.
Morever, OWIN middleware can only be placed up to the PreHandlerExecute
stage, and if the last middleware in the OWIN pipeline calling next middleware, it will continue with IIS Features (e.g DirecttoryListingModule, etc). If developers are not aware, this may result in an expected behaviour, most likely an HTTP Error 403.14 - Forbidden
error. The solution to this is to make sure the last middleware in the OWIN pipeline is a terminating middleware, which is not calling the next middleware. Otherwise, the IIS features, such as static files or directory listing, need to be activated so they will not fail.
Part 4 - Hosting OWIN Katana Application
When developing an OWIN Katana application, you will need to decide how to run or host the application. An OWIN Katana application can run as a console application, desktop application (e.g., WinForms application), or Windows service. The application is responsible for creating the server or the listener, and this hosting scenario is called self hosting. Alternatively, the application can be hosted with the IIS as a web application, or with a more lightweight OwinHost
. All these hosting scenarios will be explored in this part. The projects are created using the Visual Studio Enterprise 2017 Update 3. Most of the project templates will have the target framework in the name (e.g., .NET Framework, .NET Core, .NET Standard). Lower version Visual Studios normally do not have the target framework in their project template names, and target .NET Framework by default. OWIN Katana has a dependency on .NET Framework, thus all projects in this part target .NET Framework.
4.1. Self Hosting as a Console Application
Self hosting as a console application, the listener or server can start when the program starts or when is triggered by a command line interaction. The project below demonstrates the former one.
- Create a Console App (.NET Framework) project, and give a name
Owin.SelfHosting.Console
. Use an equivalent project template if using a different version of Visual Studio. - Add a reference to the nuget package
Microsoft.Owin.SelfHost
, which will add following references to the project.
Owin
Microsoft.Owin
Microsoft.Owin.Diagnostic
Microsoft.Owin.Hosting
Microsoft.Owin.Host.HttpListener
Microsoft.Owin.Host.SelfHost
- Create a
WelcomeOptions
class in a separate file. This class is used later in the WelcomeMiddleware
class.
namespace Owin.SelfHosting.Console
{
internal class WelcomeOption
{
public string HostName { get; set; }
public string Welcome { get; set; }
public WelcomeOption(string hostName, string welcome)
{
HostName = hostName;
Welcome = welcome;
}
}
}
- Create a
WelcomeMiddleware
class in a separate file. This class is used later in the AppBuilderExtension
class.
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
namespace Owin.SelfHosting.Console
{
internal class WelcomeMiddleware : OwinMiddleware
{
private readonly WelcomeOption _option;
public WelcomeMiddleware(OwinMiddleware next, WelcomeOption option) : base(next)
{
_option = option;
}
public override async Task Invoke(IOwinContext context)
{
System.Console.WriteLine
("Http request received at " + DateTime.UtcNow.ToString());
await Next.Invoke(context);
string welcome = string.Format("I am {0}. {1}{2}",
_option.HostName, _option.Welcome, Environment.NewLine);
await context.Response.WriteAsync(welcome).ConfigureAwait(false);
}
}
}
- Create an
AppBuilderExtensions
class in a separate file. This class contains extension methods which allow developers to register the WelcomeMiddleware
using the UseWelcome
method instead of the generic Use
method. The first method takes no argument and calls the second method by passing a default value.
namespace Owin.SelfHosting.Console
{
internal static class AppBuilderExtensions
{
public static IAppBuilder UseWelcome(this IAppBuilder appBuilder)
{
return appBuilder.UseWelcome(new WelcomeOption("Peter", "Welcome to this site"));
}
public static IAppBuilder UseWelcome
(this IAppBuilder appBuilder, WelcomeOption option)
{
return appBuilder
.Use(typeof(WelcomeMiddleware), option);
}
}
}
- Create a
Startup
class in a separate file. This class is required when creating a listener.
namespace Owin.SelfHosting.Console
{
internal class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.UseWelcome();
}
}
}
- In the
Program
class, put the code below:
using Microsoft.Owin.Hosting;
namespace Owin.SelfHosting.Console
{
internal class Program
{
public static void Main(string[] args)
{
string hostUrl = "http://localhost:9000/console";
using (WebApp.Start<Startup>(""))
{
System.Console.WriteLine
(string.Format("Start Listening at {0} ...", hostUrl));
System.Console.ReadKey();
}
}
}
}
- Run the application, and wait until it displays
'Start Listening at http://localhost:9000/console ...'
- To test the application, run any browsers and type
'http://localhost:9000/console'
- The browser should display
'I am Peter. Welcome to this site'
and the console application should display 'Http request received at [request timestamp]'
Self hosting as a winforms application, the server or listener can start when the program starts or when is trigged by an UI interaction. The project below demonstrates the latter one. The application displays a form containing a Start button and a Textbox
showing server activity logs. The server starts when the Start button is clicked. The only difference with self hosting as a console application is the code, which is previously in the Program
class, is moved to the event handler of Start button.
- Create a
Windows Forms App (.NET Framework)
project and give a name Owin.SelfHosting.WinForms
. Use an equivalent project template if using a different version of Visual Studio. - Rename
Form1
to Main
, and change the Text
property of the form to 'Main
'. - In the
Main
form, create a button btnStart
with Text
set to 'Start
', and create a Textbox txtLog
with Multiline
set to 'True
'. - Follow step 2 to step 6 from 4.1. Self hosting as a Console Application but change the namespace to
Owin.SelfHosting.WinForms
. - Create a
ControlWriter
class in a separate file. The function of this class is to redirect System.Console
output to a UI control, which is the txtLog
for this project.
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace Owin.SelfHosting.WinForms
{
public class ControlWriter : TextWriter
{
private Control textbox;
public ControlWriter(Control textbox)
{
this.textbox = textbox;
}
public override void Write(string value)
{
if (textbox.InvokeRequired)
{
textbox.Invoke(new Action<char>(Write), value);
}
else
{
textbox.Text += value;
}
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
}
- In the
btnStart
click event handler, put the code below. The btnStart
button functions as a switch to start or stop the listener. When it starts, it also redirects the System.Console
output to the txtLog
.
private IDisposable _server;
private void btnStart_Click(object sender, EventArgs e)
{
if (btnStart.Text == "Start")
{
txtLog.Clear();
System.Console.SetOut(new ControlWriter(txtLog));
string hostUrl = "http://localhost:9000/winforms";
_server = WebApp.Start<Startup>(hostUrl);
System.Console.WriteLine(string.Format("Start listening at {0} ...", hostUrl));
btnStart.Text = "Stop";
}
else
{
_server.Dispose();
btnStart.Text = "Start";
txtLog.Clear();
}
}
- Run the application, and click the
Start
button, and wait until the txtLog
shows 'Start Listening at http://localhost:9000/winforms ...'
- To test the application, run any browsers and type
'http://localhost:9000/winforms'
- The browser should display
'I am Peter. Welcome to this site'
and the txtLog
should show 'Http request received at [request timestamp]'
4.3. Self Hosting as a Windows Service
In order to self hosting as a Windows service, the listener is created when the service starts, and disposed when the service stops.
- Create a
Console App(.NET Framework)
project and give a name Owin.SelfHosting.WindowsService
. Use an equivalent project template if using a different version of Visual Studio. - Follow step 2 to step 6 from 4.1. Self Hosting as a Console Application. but change the namespace to
Owin.SelfHosting.WindowsService
. - Add a reference to the Framework Assembly
System.ServiceProcess
. - Add a new item of type
'Windows Service'
, and give it a name OwinService
. This will create a OwinService
class in a separate file. Modify it with codes below:
using System;
using System.ServiceProcess;
using Microsoft.Owin.Hosting;
namespace Owin.SelfHosting.WindowsService
{
partial class OwinService : ServiceBase
{
private IDisposable _server;
public OwinService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
string hostUrl = "http://localhost:9000/windowsservice";
_server = WebApp.Start<Startup>(hostUrl);
}
protected override void OnStop()
{
_server.Dispose();
}
}
}
- Double click the
OwinService
in the solution explorer to show a designer form, right click on the designer form and select 'Add Installer'
and a ProjectInstaller
class will be created - Double click the
ProjectInstaller
in the solution explorer to show a designer form, and there will be two components: serviceProcessIntaller1
and serviceInstaller1
- Set
serviceProcessInstaller1
's Account
property to 'LocalSystem
' - Make sure
serviceInstaller1
's ServiceName
is set to 'OwinService
'. Optionally, the serviceInstaller1
's DisplayName
and StartType
can be set for the windows service's preferences. - Compile the project, and locate the output directory. By default, it should be in 'bin/Debug' or 'bin/Release' folder.
- Run
'installUtil.exe Owin.SelfHosting.WindowsService.exe'
to register OwinService
. The InstallUtil
can be found in the .NET Framework folder %WINDIR%\Microsoft.NET\Framework[64]\v[framework_version]. In my machine, the location of this file is 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\'. - Check the services list or type
'Services.msc'
in the command prompt and find the 'OwinService'
, select and start it. - To test the service, runs any browsers and type
'http://localhost:9000/windowsservice'
. - The browser should display
'I am Peter. Welcome to this site'
4.4. IIS Hosting
- Create an
ASP.NET Web Application (NET. Framework)
project, and select an Empty template
, and give a name Owin.IISHosting
. Use an equivalent project template if using a different version of Visual Studio. - Add a reference to the nuget package
Microsoft.Owin.Host.SystemWeb
, which will add following references to the project:
Owin
Microsoft.Owin
Microsoft.Owin.Host.SystemWeb
- Follow step 3 to step 6 from Self Hosting as a Console Application, but change the namespace to
Owin.IISHosting
, and with a modification to WelcomeMiddleware
class to make sure it does not call the next middleware.
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
namespace Owin.IISHosting
{
internal class WelcomeMiddleware : OwinMiddleware
{
private readonly WelcomeOption _option;
public WelcomeMiddleware(OwinMiddleware next, WelcomeOption option) : base(next)
{
_option = option;
}
public override async Task Invoke(IOwinContext context)
{
System.Console.WriteLine
("Http request received at " + DateTime.UtcNow.ToString());
string welcome = string.Format("I am {0}. {1}{2}",
_option.HostName, _option.Welcome, Environment.NewLine);
await context.Response.WriteAsync(welcome).ConfigureAwait(false);
}
}
}
- Open the project properties in the designer view, in the tab
'Web'
, in the section 'Server'
, check 'apply server settings to all users (store in project file)'
, select 'IIS Express'
from the combo box, and type 'http://localhost:9000/iishosting'
in the 'Project Url'
. - Run the application from the Visual Studio, the
IIS Express
will start and host the application. You can control the applications run by the IIS Express
by clicking the its icon in the system tray. - The Visual Studio will also start a browser and pointing at
'http://localhost:9000/iishosting'
and it should display 'I am Peter. Welcome to this site'
.
4.5. Hosting with an OwinHost
Katana also ships a nuget package OwinHost
containing an implementation of an alternative host, which can be used to replace the IIS host. When you create an ASP Web Application project and add this package, it will add a custom host called 'OwinHost'
to the project. The application then can be configured to run using the 'Owin Host'
instead of the 'IIS Express'
.
- Follow step 1 to step 3 from the 4.4. IIS Hosting, but change the namespace to
Owin.OwinHosting
. - Install the nuget package
OwinHost
(3.1.0), this will add a reference to the package and add 'Servers'
entry in the 'WebProjectProperties'
in the project file (.csproj). This essential adds the 'OwinHost'
as a custom host. - Open the project properties in the designer view, in the tab
'Web'
, in the section 'Server'
, check 'apply server settings to all users (store in project file)'
, select the 'OwinHost'
from the combo box, and type 'http://localhost:9000/owinhosting'
in the 'Project Url'
. - Run the application from the Visual Studio, and the
'OwinHost'
should start and host the application. - The Visual Studio will also start a browser and pointing at
'http://localhost:9000/owinhosting'
and it should display 'I am Peter. Welcome to this site'
.
When hosting with the OwinHost
, the nuget package Microsoft.Owin.SelfHost
can be used instead of using the Microsoft.Owin.Host.SystemWeb
package. The OwinHost
host can work with both listeners in the Microsoft.Owin.Host.SystemWeb
and in the Microsoft.Owin.Host.HttpListener
(which is in Microsoft.Owin.SelfHost
package).
Part 5 -Web API Development with OWIN Katana
While it is possible to develop a RESTful API with OWIN Katana, it does not give enough leverage or abstraction to do it effectively. Writing middleware in OWIN Katana is best for web frameworks or cross cutting concerns of web applications. When developing a RESTful API, it is best to use web frameworks such as Nancy
or Microsoft's Web API
, which will be used in this part.
Web API can be used for both ASP.NET applications and OWIN Katana applications. The benefit of developing using OWIN Katana is the flexibility to choose from various hosting scenarios as explained in the previous part. ASP.NET (up to 4.6) applications can only be hosted with the IIS. Another main differences between the two is entry point to register the Web API middleware. For OWIN Katana applications, it is registered in a startup
method or a Startup
class, while for ASP.NET applications, it is registered in the Application_Startup
method of the Global.asax.
However, ASP.NET 5, which later called ASP.NET Core, applications already use a Startup
class, which follow OWIN Katana 'style
'.
When implementing Web API with OWIN Katana, Web API is just a middleware in the pipeline, which can be registered by UseWebApi
method, taking one argument HttpConfiguration
. Web API provides high level constructs to work with when developing an API such as: controller, router, formatter, query parsing model binding, and most of them can be configured via HttpConfiguration
.
The following link shows the pipeline structure inside Web API (Web API 2.0). The first stage of WebAPI is HttpMesageHandlers
containing three type of handlers. Delegating
handlers, similar to middleware, can be injected via the HttpConfiguration
. The HttpRoutingDispatcher
handler supports pipeline branching, which can be configured via HttpConfiguration
. The last handler, HttpControllerDispatcher
routes a request to a correct controller. Web API controller must derive from a ApiController
class.
The second stage of WebAPI is a controller stage. A lot of processing takes place before a controller method is executed. Authentication
and Authorization
filters are executed if present. Next, Model binding attempts to map values from a request (URI, headers and body) to parameters, which can be passed into a controller. Then, Action
filters, if present, are executed before the controller method. On the way out, Action
filters, Result Conversion, and Exception
filters are executed. In the following section, simple Web API projects will be created with self hosting and IIS hosting.
5.1. Web API with Self Hosting
Web API self hosting as a console application.
- Create a
Console App (.NET Framework)
project, give it a name Owin.WebApi.SelfHosting.
Use an equivalent project template if using a different version of Visual Studio. - Install the nuget package
Microsoft.AspNet.WebApi.OwinSelfHost
(3.1.0), which will add following references:
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Microsoft.AspNet.WebApi.OwinSelfHost
Microsoft.Owin
Microsoft.Owin.Host.HttpListener
Microsoft.Owin.Hosting
Newtonsoft.Json
Owin
Alternatively, install the nuget packages Microsoft.AspNet.WebApi.Owin
(5.2.3) and Microsoft.Owin.SelfHost
(3.1.0), which will add following references:
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin
Microsoft.Owin.Diagnostics
Microsoft.Owin.Host.HttpListener
Microsoft.Owin.Hosting
Microsoft.Owin.SelfHost
Newtonsoft.Json
Owin
- Create a
Startup
class in a separate file. The startup
method will register Web API
with the configured HttpConfiguration
. The MapHttpAttributeRoutes
means the API routes based on the route attributes on the controllers.
using System.Web.Http;
namespace Owin.WebApi.SelfHosting
{
internal class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes();
httpConfig.EnsureInitialized();
appBuilder
.UseWebApi(httpConfig);
}
}
}
- Create a
UserController
by deriving from a ApiController
class. Mark the class with a RoutePrefix
attribute, and the method with Route
and HttpGet
attributes. The route for this call is GET '/User/1234'. The Model Binding will assign the 'identifier'
with the value after '/User'. This method returns a json response if the identifier = 1234
, otherwise it returns a NotFound
response.
using System.Web.Http;
namespace Owin.WebApi.SelfHosting
{
[RoutePrefix("User")]
public class UserController : ApiController
{
[Route("{identifier}")]
[HttpGet]
public IHttpActionResult GetUser(string identifier)
{
if (identifier == "1234")
{
return Json(new
{
Givenname = "Peter",
Surname = "Smith",
Age = 45
});
}
return NotFound();
}
}
}
- Create a
Program
class in a separate file.
using System;
using Microsoft.Owin.Hosting;
using Owin.WebApi.SelfHosting;
namespace Owin.WebApi.SelfHost
{
internal class Program
{
public static void Main(string[] args)
{
string hostUrl = "http://localhost:9000/webapi/selfhosting";
using (var server = WebApp.Start<Startup>(hostUrl))
{
Console.WriteLine(string.Format("Start Listening at {0} ...", hostUrl));
Console.ReadKey();
}
}
}
}
- Run the application, wait until it displays
'Start Listening at http://localhost:9000/webapi/selfhosting ...'
- To test the application, run any browsers and type
'http://localhost:9000/webapi/selfhosting/user/1234'
- The browser should display a json response containing user detail
5.2. Web API with IIS Hosting
- Create an
ASP.NET Web Application (.NET Framework)
project, choose an Empty
template. Use an equivalent project template if using a different version of Visual Studio. - There are two nuget packages to install. First, add the
Microsoft.Owin.Host.SystemWeb
package, which will add the following references:
Microsoft.Owin
Microsoft.Owin.Host.SystemWeb
Owin
- Second, add the
Microsoft.AspNet.WebApi.Owin
package, which will add the following references:
Microsoft.AspNet.WebApi.Client
Microsoft.AspNet.WebApi.Core
Microsoft.AspNet.WebApi.Owin
Newtonsoft.Json
- Follow step 3 and step 4 from
5.2. Web API with Self Hosting
, but change the namespace to Owin.WebApi.IISHosting
- Open the project properties in a designer view, in the tab
'Web'
, section 'Server'
, check 'apply server settings to all users (store in project file)'
, select 'IIS Express'
from the combo box, and type 'http://localhost:9000/webapi/iishosting'
in the 'Project Url'
. - To test the application, run any browsers and type
'http://localhost:9000/webapi/iishosting/user/1234'
. - The browser should display a json response containing a user detail.
Part 6 - From OWIN Katana to ASP.NET Core
A lot of changes were happening in .NET web development technology in recent years. In line with the web developments in other languages, the tendency is to develop smaller, focused components which were released more frequently.
OWIN is the beginning and the foundation of architectural design toward more modular, lightweight, open platform and open source Microsoft web products. Katana is the Microsoft attempt to provide an implementation which developer can experience with. Feedbacks from the Katana implementation and usages were fed into the development of next Microsoft web product, ASP.NET 5.
ASP.NET 5 was first released in 2015. It combines the best features from the Web API and MVC 5 into a single product called MVC6. At the same time, Microsoft also introduced a new framework called .NET Core 5, which also targeted other platforms such as Linux and MacOS. The .NET Core Framework is a strip down version .NET Framework and is distributed as a collection of nuget packages. ASP.NET 5 can run on both .NET Core 5 and .NET Framework.
In the beginning of January 2016, the ASP.NET 5 name was changed to ASP.NET Core, in a move to make a distinction from the previous Microsoft ASP.NET 4.6 which targets .NET Framework only. At the moment, ASP.NET Core 2 product was released in August 2017 alongside with .NET Core 2 Framework, and supported in Visual Studio 2017 update version 3. However, the .NET Core 2 Framework need to be downloaded separately.
ASP.NET Core natively supports OWIN , but it does not support the Katana implementation which is .NET Framework dependant. Even though ASP.NET Core follows the same concepts in OWIN and Katana, it has its own abstraction. For example, it uses an IApplicationBuilder
instead of an IAppBuilder
, an HttpContext
instead of IDictionary<string, object>
.
ASP.NET Core 2 can register OWIN middleware by using the UseOwin
method from the Microsoft.AspNetCore.Owin
nuget packages. Using this method only a pure OWIN middleware, in the form of delegate Func<AppFunc, AppFunc>
, can be added into the pipeline. As shown in the code below, the delegate argument pipeline
is used to register the OWIN Middleware.
using AppFunc = Func<IDictionary<string, object>, Task>;
...
public void Configure(IApplicationBuilder appBuilder)
{
appBuilder
.UseOwin(pipeline=>
{
pipeline(OwinMiddlewareOne);
pipeline(OwinMiddlewareTwo);
});
}
public AppFunc OwinMiddlewareOne(AppFunc next)
{
AppFunc middlewareBody = async (env) =>
{
...
await next.Invoke(env);
...
}
return middlewareBody;
}
public AppFunc OwinMiddlewareTwo(AppFunc next)
{
AppFunc middlewareBody = async (env) =>
{
...
await next.Invoke(env);
...
}
return middlewareBody;
}
In order to fully reuse OWIN Katana middleware, an extension method in the following code project article can be used. However, this will require a dependency on the .NET Framework, as Katana is .NET Framework dependant.
There are also a number changes and departure from OWIN Katana and Web API implementation such as:
- Starting up server or listener change from using static method
WebApp.Start
to using IWebHost
's Run()
or Start()
. It has a WebHostBuilder
to build the object before running it. The UseUrls
and UseStartup<T>
are used to specify the listener addresses and a Startup
class respectively. The WebHostBuilder
also has many other methods. For example, the builder can specify the hosting model, such as integration with IIS via UseIISIntegration
, Configuration via UseConfiguration
, Server type via UseServer
, Environment via UseEnvironment
, Content Root or Web Root directory via UseContentRoot
and UseWebRoot
or capture an error and display an error page by default via CaptureStartupErrors
. In the ASP.NET Core 2, the WebHost
has a static
method CreateDefaultBuilder
, which automatically configures to use the Kestrel
listener (to allow cross platform), load a configuration, set content root to Directory.GetCurrentDirectory
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
- The
Startup
class changes. The IAppBuilder
is replaced with the IApplicationBuilder
. The Startup
class now has two methods. The first method is void ConfigureServices(IServiceCollection services)
, which is basically an IOC (Inversion of Control) container, where all dependencies are injected. The second method is void Configure(IApplicationBuilder app)
, which is for registering middleware. Additionally, the Startup
class can also takes an optional IHostingEnvironment
and/or an optional ILoggerFactory
arguments in the Configure
method or the in the Startup
constructor. To use the Web API framework, the service need to be added first by calling the AddMvc
in the first method, and then it can be used by calling the UseMvc
in the second method. Also, the HttpConfiguration
is replaced with a delegate which accepts an IRouteBuilder
argument.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder appBuilder,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory
.AddConsole();
appBuilder
.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
}
}
- The middleware class has the same structure as the native OWIN middleware class. However, it uses a
HttpContext
instead of a IDictionary<string, object>
and the 'next
' component is a delegate called RequestDelegate
instead of a Func<IDictionary<string, object>, Task>.
The HttpContext
is not the old System.Web
object, but a new lightweight object. The object can be converted to an OWIN environment object by using the OwinEnvironment
class from the Microsoft.AspNetCore.Owin
package. The RequestDelegate
is equivalent to Func<HttpContext, Task>
. Here is the example of the new middleware class.
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
return this._next(context);
}
}
Overall, ASP.NET core applies core concepts laid out in the OWIN, even though it departs from the Katana implementation. Note: all projects for Part 4 - OWIN Katana Hosting Application
and Part 5 - Web API Development with OWIN Katana
can be downloaded from github.
References
- OWIN website
- Microsoft ASP.NET Web API (2.0) Poster
- OWIN Startup class detection
- Using OWIN pipeline in ASP.NET Core
- Katana, ASP.NET 5, and bridging the gap
- Katana source files
History
- 19th September, 2017: Initial version