A middleware should always invoke the next?

2019-03-27 19:21发布

问题:

I've been trying to understand how ASP.NET 5 pipeline middlewares really work. A middleware, as I know, is just a Func<RequestDelegate, RequestDelegate>, which is a pointer to a method that receives a reference to the next request delegate and returns a new one that wraps the next. We can of course, use a class to represent a middleware, something like:

public class MyMiddleware
{
    private readonly _next;

    public MyMiddleware(RequestDelegate next)
    {
        if (next == null)
        {
            throw new ArgumentNullException("next");
        }

        _next = next;
    }

    public Task Invoke(HttpContext context)
    {
        // New request delegate code here which can wrap the next one on the pipeline
    }
}

Since the RequestDelegate is a delegate that can hold references to methods which receives one HttpContext and returns a Task the Invoke method is the request delegate to be returned and which has access to the next one on the pipeline.

We have then, when coding a middleware, access to the next component of the pipeline, but there is a doubt I have. In the beginning I thought the ideal was always to work the following way:

  • Check if the middleware can handle the request
  • If it can, do whatever must be done with the HttpContext
  • Call the next middleware on the pipeline

So that when I studied this for the first time I thought each middleware should always call the next one. But doing this led to strange behavior as discussed on this question.

Also looking at the source code of some middlewares I see some of them follow another steps:

  • Check if the middleware can handle the request
  • If it can, do whatever must be done with the HttpContext and that's all
  • If not, and only if not call the next one

Is this the real idea of using middlewares? Which way is the correct approach at this? Each middleware do what must be done with the request and always invoke the next or if a midleware can handle the request it doesn't invoke the next anymore?

I believe a middleware should call the next only if it can't handle the request. The reason I think that is because if not, there would be coupling between the middlewares on the pipeline. So that to process the request the middleware would need to be aware of what the previous one did to avoid messing everything. Is this conclusion right?

回答1:

Middleware exist to make the request pipeline modular, meaning that you can add/remove/replace parts from it as long as you respect the contract. For example, if your application serves some files without any caching, you can add a middleware at the front of the pipeline without altering the rest. They are building blocks.

A middleware can:

  1. Do nothing and pass the request further (e.g. a middleware that is applicable only to POST requests but the current one is GET)
  2. Do nothing to the request, do something else instead and pass it further (e.g. logging)
  3. Do something to the request and pass the request further (e.g. get an authentication token and convert it to an identity, or remove some sensitive information from the request)
  4. End the pipeline and not pass the request further (e.g. StaticFileMiddleware which just returns the file, or MVC when a route matches)

Probably answering your other question too: there are two types of middleware:

  1. Middleware that are designed to do something and pass the data along further (etc. auth, cookies, validation, logging etc)
  2. Middleware that complete the pipeline (static file, MVC, etc).

Of course, some might do both depending on the context. For example auth can end the pipeline if the credentials are incorrect but continue otherwise.

The author of the middleware must decide if the next middleware (if any) should be invoked. In the case of the middleware in your question which returns a message, it should not invoke the next one.