.NET Core開發日誌——RequestDelegate

本文主要是對.NET Core開發日誌——Middleware的補遺,可是會從看起來平平無奇的RequestDelegate開始敘述,因此以其做爲標題,也是合情合理。html

RequestDelegate是一種委託類型,其全貌爲public delegate Task RequestDelegate(HttpContext context),MSDN上對它的解釋,"A function that can process an HTTP request."——處理HTTP請求的函數。惟一參數,是最熟悉不過的HttpContext,返回值則是表示請求處理完成的異步操做類型。app

能夠將其理解爲ASP.NET Core中對一切HTTP請求處理的抽象(委託類型自己可視爲函數模板,其實現具備統一的參數列表及返回值類型),沒有它整個框架就失去了對HTTP請求的處理能力。框架

而且它也是構成Middleware的基石。或者更準確地說參數與返回值都是其的Func<RequestDelegate, RequestDelegate>委託類型正是維持Middleware運轉的核心齒輪。異步

組裝齒輪的地方位於ApplicationBuilder類以內,其中包含着全部齒輪的集合。async

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

以及添加齒輪的方法:ide

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}

在Startup類的Configure方法裏調用以上ApplicationBuilder的Use方法,就能夠完成一個最簡單的Middleware。函數

public void Configure(IApplicationBuilder app)
{
    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };
        
    });
}

齒輪要想變成Middleware,在完成添加後,還須要通過組裝。ui

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

    foreach (var component in _components.Reverse())
    {
        app = component(app);
    }

    return app;
}

Build方法裏先定義了最底層的零件——app,context => { context.Response.StatusCode = 404; return Task.CompletedTask; },這段代碼意味着,若是沒有添加任何Middleware的話,ASP.NET Core站點啓動後,會直接出現404的錯誤。this

接下的一段,遍歷倒序排列的齒輪,開始正式組裝。日誌

在上述例子裏,只使用了一個齒輪:

_ =>
{
    return context =>
    {
        return context.Response.WriteAsync("Hello, World!");
    };
    
}

那麼第一次也是最後一次循環後,執行component(app)操做,app被從新賦值爲:

context => context.Response.WriteAsync("Hello, World!");

組裝的結果即是app的值。

這個組裝過程在WebHost進行BuildApplication時開始操做。今後方法的返回值類型能夠看出,雖然明義上是建立Application,其實生成的是RequestDelegate。

private RequestDelegate BuildApplication()
{
    try
    {
        ...

        var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
        var builder = builderFactory.CreateBuilder(Server.Features);
        ...
        Action<IApplicationBuilder> configure = _startup.Configure;
        ...

        configure(builder);

        return builder.Build();
    }
    ...
}

而這個RequestDelegate最終會在HostingApplication類的ProcessRequestAsync方法裏被調用。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
    ...

    var application = BuildApplication();

    ...
    var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
    ...
}    

public HostingApplication(
    RequestDelegate application,
    ILogger logger,
    DiagnosticListener diagnosticSource,
    IHttpContextFactory httpContextFactory)
{
    _application = application;
    _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
    _httpContextFactory = httpContextFactory;
}

public Task ProcessRequestAsync(Context context)
{
    return _application(context.HttpContext);
}

上例中的執行結果便是顯示Hello, World!字符。

404的錯誤再也不出現,意味着這種Middleware只會完成本身對HTTP請求的處理,並不會將請求傳至下一層的Middleware。

要想達成不斷傳遞請求的目的,須要使用另外一種Use擴展方法。

public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{
    return app.Use(next =>
    {
        return context =>
        {
            Func<Task> simpleNext = () => next(context);
            return middleware(context, simpleNext);
        };
    });
}

在實際代碼中能夠這麼寫:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };
    });
}

如今多了個Middleware,繼續上面的組裝過程。app的值最終被賦值爲:

async context =>
{
    Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); 

    await context.Response.WriteAsync("I am a Middleware!\n");
    await simpleNext.Invoke();
};

顯示結果爲:

I am a Middleware!
Hello, World!

下面的流程圖中能夠清楚地說明這個過程。

若是把await next.Invoke()註釋掉的話,

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        //await next.Invoke();
    });

    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };

    });
}

上例中第一個Middleware處理完後,不會繼續交給第二個Middleware處理。注意如下simpleNext的方法只被定義而沒有被調用。

async context =>
{
    Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); 

    await context.Response.WriteAsync("I am a Middleware!\n");
};

這種狀況被稱爲短路(short-circuiting)。

作短路處理的Middleware通常會放在全部Middleware的最後,以做爲整個pipeline的終點。

而且更常見的方式是用Run擴展方法。

public static void Run(this IApplicationBuilder app, RequestDelegate handler)
{
    ...

    app.Use(_ => handler);
}

因此能夠把上面例子的代碼改爲下面的形式:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

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

除了短路以外,Middleware處理時還能夠有分支的狀況。

public void Configure(IApplicationBuilder app)
{
    app.Map("/branch1", ab => {
        ab.Run(async context =>
        {
            await context.Response.WriteAsync("Map branch 1");
        });
    });

    app.Map("/branch2", ab => {
        ab.Run(async context =>
        {
            await context.Response.WriteAsync("Map branch 2");
        });
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

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

URL地址後面跟着branch1時:

URL地址後面跟着branch2時:

其它狀況下:

Map擴展方法的代碼實現:

public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
{
    ...

    // create branch
    var branchBuilder = app.New();
    configuration(branchBuilder);
    var branch = branchBuilder.Build();

    var options = new MapOptions
    {
        Branch = branch,
        PathMatch = pathMatch,
    };
    return app.Use(next => new MapMiddleware(next, options).Invoke);
}

建立分支的辦法就是從新實例化一個ApplicationBuilder。

public IApplicationBuilder New()
{
    return new ApplicationBuilder(this);
}

對分支的處理則是封裝在MapMiddleware類之中。

public async Task Invoke(HttpContext context)
{
    ...

    PathString matchedPath;
    PathString remainingPath;

    if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
    {
        // Update the path
        var path = context.Request.Path;
        var pathBase = context.Request.PathBase;
        context.Request.PathBase = pathBase.Add(matchedPath);
        context.Request.Path = remainingPath;

        try
        {
            await _options.Branch(context);
        }
        finally
        {
            context.Request.PathBase = pathBase;
            context.Request.Path = path;
        }
    }
    else
    {
        await _next(context);
    }
}

說到MapMiddleware,不得不說起各類以Use開頭的擴展方法,好比UseStaticFiles,UseMvc,UsePathBase等等。

這些方法內部都會調用UseMiddleware方法以使用各種定製的Middleware類。以下面UsePathBase的代碼:

public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase)
{
    ...

    // Strip trailing slashes
    pathBase = pathBase.Value?.TrimEnd('/');
    if (!pathBase.HasValue)
    {
        return app;
    }

    return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
}

而從UseMiddleware方法中能夠獲知,Middleware類需知足二者條件之一才能被有效使用。其一是實現IMiddleware,其二,必須有Invoke或者InvokeAsync方法,且方法至少要有一個HttpContext類型參數(它還只能是放第一個),同時返回值須要是Task類型。

internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
    if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
    {
        ...

        return UseMiddlewareInterface(app, middleware);
    }

    var applicationServices = app.ApplicationServices;
    return app.Use(next =>
    {
        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        var invokeMethods = methods.Where(m =>
            string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
            || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
            ).ToArray();

        ...

        var ctorArgs = new object[args.Length + 1];
        ctorArgs[0] = next;
        Array.Copy(args, 0, ctorArgs, 1, args.Length);
        var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
        if (parameters.Length == 1)
        {
            return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
        }

        var factory = Compile<object>(methodinfo, parameters);

        return context =>
        {
            var serviceProvider = context.RequestServices ?? applicationServices;
            ...
            return factory(instance, context, serviceProvider);
        };
    });
}

對ASP.NET Core中Middleware的介紹到此終於能夠告一段落,但願這兩篇文章可以爲讀者提供些許助力。

相關文章
相關標籤/搜索