官术网_书友最值得收藏!

Understanding the OWIN pipeline

Previous versions of ASP.NET had a very close relationship with Internet Information Services (IIS), Microsoft's flagship web server that ships with Windows. In fact, IIS was the only supported way to host ASP.NET.

Wanting to change this, Microsoft defined the Open Web Interface for .NET (OWIN) specification, which you can read about at http://owin.org. In a nutshell, it is the standard for decoupling server and application code, and for the execution pipeline for web requests. Because it is just a standard and knows nothing about the web server (if any), it can be used to extract its features.

.NET Core borrowed heavily from the OWIN specification. There are no more Global.asax, web.config, or machine.config configuration files, modules, or handlers. What we have is the following:

  • The bootstrap code in Program.Main declares a class that contains a convention-defined method (Startup will be used if no class is declared).
  • This conventional method, which should be called Configure, receives a reference to an IApplicationBuilder instance (it can take other services to be injected from the service provider).
  • You then start adding middleware to the IApplicationBuilder; this middleware is what will handle your web requests.

A simple example is in order. First, the bootstrap class, which is by default named Program:

public class Program
{
    public static void Main(string [] args) =>
CreateWebHostBuilder(args).Build().Run();

public static IHostBuilder CreateHostBuilder(string [] args) =>
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
}); }

Things can get more complicated, but don't worry too much about it now. Later on, I will explain what this all means. For the time being, it's enough to know that we are leveraging a Host to host Kestrel (the default host), and passing a conventional class called Startup. This Startup class looks like this (in a simplified way):

public class Startup
{
public IConfiguration Configuration { get; }

{
this.Configuration = configuration;
}

public void Configure(IApplicationBuilder app)
{
app.Run(async (context) => {
await context.Response.WriteAsync("Hello, OWIN World!");
}
}
}

There are a couple of things here that deserve an explanation. First, you will notice that the Startup class does not implement any interface or inherit from an explicit base class. This is because the Configure method does not have a predefined signature, other than its name, taking as its first parameter an IApplicationBuilder. For example, the following is also allowed:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... }

This version even gives you more than what you asked for. But I digress. The IApplicationBuilder interface defines a Run method. This method takes a RequestDelegate parameter, which is a delegate definition that accepts an HttpContext (remember that?) as its sole parameter and returns a Task. In my example, we made it asynchronous by adding async and await keywords to it, but it need not be so. All you have to do is make sure you extract whatever you want from the HttpContext and write whatever you want to it—this is your web pipeline. It wraps both the HTTP request and response objects, and we call it middleware.

The Run method is a full-blown pipeline on its own, but we can plug other steps (middleware) into the pipeline by using the (pun intended) Use method:

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello from a middleware!");
    await next();
});       

This way, we can add multiple steps, and they all will be executed in the order they were defined:

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("step 1!");
    await next();
});

app.Use(async (context, next) => { await context.Response.WriteAsync("step 2!"); });

Just keep in mind that the order does matter here; the next example shows this:

app.Use(async (context, next) =>
{
    try
{
//step 1 await next();
}
catch (Exception ex)
{
await context.Response.WriteAsync($"Exception {ex.Message} was
caught!");
} });
app.Use(async (context, next) => {
//step 2
throw new Exception(); });

Because the first step was added before the second, it wraps it, so any exceptions thrown by step two will be caught by step one; if they were added in a different order, this wouldn't happen.

The Use method takes anHttpContextinstance as its parameter and returns aFunc<Task>, which is normally a call to the next handler, so that the pipeline proceeds.

We could extract the lambda to its own method, like this:

async Task Process(HttpContext context, Func<Task> next)
{
    await context.Response.WriteAsync("Step 1");
    await next();
}

app.Use(Process);

It is even possible to extract the middleware to its own class and apply it using the generic UseMiddleware method:

public class Middleware
{
    private readonly RequestDelegate _next;

public Middleware(RequestDelegate next) { this._next = next; }

public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync("This is a middleware class!"); } }
//in Startup.Configure
app.UseMiddleWare<Middleware>();

In this case, the constructor needs to take as its first parameter a pointer to the next middleware in the pipeline, as a RequestDelegate instance.

I think by now you've got the picture: OWIN defines a pipeline to which you can add handlers which are then called in sequence. The difference between Run and Use is that the former ends the pipeline—that is, it won't call anything after itself.

The following diagram (from Microsoft) clearly shows this:

Image taken from https://docs.microsoft.com/en-us/dotnet/architecture/blazor-for-web-forms-developers/middleware

The first middleware, in a way, wraps all of the next ones. For example, imagine that you want to add exception handling to all the steps in the pipeline. You could do something like this:

app.Use(async (context, next) =>
{
    try
    {
        //log call
        await next(context);
    }
    catch (Exception ex)
    {
        //do something with the exception
    }
await context.Response.WriteAsync("outside an exception handler"); });

The call to next() is wrapped in a try...catch block, so any exception that may be thrown by another middleware in the pipeline, as long as it was added after this one, will be caught.

You can set the status code of a response, but be aware that, if an exception is thrown, it will be reset to 500 Server Error!

You can read more about Microsoft's implementation of OWIN at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/owin.

Why is OWIN important? Well, because ASP.NET Core (and its MVC implementation) are built on it. We will see later that in order to have an MVC application, we need to add the MVC middleware to the OWIN pipeline in the Startup class's Configure method, normally as shown in the following code, using the new endpoint routing and the default route:

    {
endpoints.MapDefaultControllerRoute();
});

As you know, this book talks essentially about the MVC pattern, but we could go equally with this kind of middleware, without any MVC stuff; it's just that it would be much harder to tackle complexity, and MVC does a very good job of that.

OWIN is essentially ASP.NET Core middleware. Everything that we add in a UseXXX extension is middleware. Let's look at how we can host an ASP.NET Core project next.

主站蜘蛛池模板: 镇坪县| 磐石市| 梁平县| 洮南市| 德格县| 黄平县| 裕民县| 满城县| 西峡县| 衡山县| 建始县| 钟山县| 桑日县| 星子县| 祁门县| 德保县| 自治县| 景泰县| 香格里拉县| 巫溪县| 古田县| 肥西县| 清原| 虎林市| 宁德市| 宁南县| 邵阳县| 永济市| 平南县| 桓仁| 敦化市| 大化| 青川县| 都安| 二连浩特市| 拜泉县| 五寨县| 古交市| 平武县| 河源市| 沛县|