ASP.NET Core 中間件基本用法


ASP.NET Core 中間件

ASP.NET Core的處理流程是一個管道,而中間件是裝配到管道中的用於處理請求和響應的組件。中間件按照裝配的先后順序執行,並決定是否進入下一個組件。中間件管道的處理流程如下圖(圖片來源於官網):

image

管道式的處理方式,更加方便我們對程序進行擴展。

使用中間件

ASP.NET Core中間件模型是我們能夠快捷的開發自己的中間件,完成對應用的擴展,我們先從一個簡單的例子了解一下中間件的開發。

Run

首先,我們創建一個ASP.NET Core 應用,在Startup.cs中有如下代碼:

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

這段代碼中,使用Run方法運行一個委托,這就是最簡單的中間件,它攔截了所有請求,返回一段文本作為響應。Run委托終止了管道的運行,因此也叫作終端中間件

Use

我們再看另外一個例子:

app.Use(async (context, next) =>
{
    //Do something here
    
    //Invoke next middleware
    await next.Invoke();
    
    //Do something here
    
});

這段代碼中,使用Use方法運行一個委托,我們可以在Next調用之前和之后分別執行自定義的代碼,從而可以方便的進行日志記錄等工作。這段代碼中,使用next.Invoke()方法調用下一個中間件,從而將中間件管道連貫起來;如果不調用next.Invoke()方法,則會造成管道短路

Map和MapWhen

處理上面兩種方式,ASP.NET Core 還可以使用Map創建基於路徑匹配的分支、使用MapWhen創建基於條件的分支。代碼如下:

private static void HandleMap(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Handle Map");
    });
}

private static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Map("/map", HandleMap);
    
    app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
               HandleBranch);
    
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

上面的代碼演示了如何使用Map和MapWhen創建基於路徑和條件的分支。另外,Map方法還支持層級的分支,我們參照下面的代碼:

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

需要注意,使用 Map 時,將從 HttpRequest.Path 中刪除匹配的Path,並針對每個請求將該線段追加到 HttpRequest.PathBase。例如對於路徑/level1/level2a,當在level1App中進行處理時,它的請求路徑被截斷為/level2a,當在level2AApp中進行處理時,它的路徑就變成/了,而相應的PathBase會變為/level1/level2a

開發中間件

看到這里,我們已經知道中間件的基本用法,是時候寫一個真正意義的中間件了。

基於約定的中間件開發

在 ASP.NET Core 官網上面提供了一個簡單的例子,通過中間件來設置應用的區域信息,代碼如下:

public void Configure(IApplicationBuilder app)
{
    app.Use((context, next) =>
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline
        return next();
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync(
            $"Hello {CultureInfo.CurrentCulture.DisplayName}");
    });
}

通過這段代碼,我們可以通過QueryString的方式設置應用的區域信息。但是這樣的代碼怎樣復用呢?注意,中間件一定要是可復用、方便復用的。我們來改造這段代碼:

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        //......

        // Call the next delegate/middleware in the pipeline
        await _next(context);
    }
}

這里定義一個委托,用於執行具體的業務邏輯,然后在Configure中調用這個委托:

app.UseMiddleware<RequestCultureMiddleware>();

這樣還是不太方便,不像我們使用app.UseMvc()這么方便,那么我們來添加一個擴展方法,來實現更方便的復用:

public static class RequestCultureMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestCulture(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestCultureMiddleware>();
    }
}

然后我們就可以這樣使用中間件了:

app.UseRequestCulture();

通過委托構造中間件,應用程序在運行時創建這個中間件,並將它添加到管道中。這里需要注意的是,中間件的創建是單例的,每個中間件在應用程序生命周期內只有一個實例。那么問題來了,如果我們業務邏輯需要多個實例時,該如何操作呢?請繼續往下看。

基於請求的依賴注入

通過上面的代碼我們已經知道了如何編寫一個中間件,如何方便的復用這個中間件。在中間件的創建過程中,容器會為我們創建一個中間件實例,並且整個應用程序生命周期中只會創建一個該中間件的實例。通常我們的程序不允許這樣的注入邏輯。

其實,我們可以把中間件理解成業務邏輯的入口,真正的業務邏輯是通過Application Service層實現的,我們只需要把應用服務注入到Invoke方法中即可。

ASP.NET Core為我們提供了這種機制,允許我們按照請求進行依賴的注入,也就是每次請求創建一個服務。代碼如下:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMyScopedService is injected into Invoke
    public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

在這段代碼中,CustomMiddleware的實例仍然是單例的,但是IMyScopedService是按照請求進行注入的,每次請求都會創建IMyScopedService的實例,svc對象的生命周期是PerRequest的。

基於約定的中間件模板

這里提供一個完整的示例,可以理解為一個中間件的開發模板,方便以后使用的時候參考。整個過程分以下幾步:

  • 將業務邏輯封裝到ApplicationService中
  • 創建中間件代理類
  • 創建中間件擴展類
  • 使用中間件

代碼如下:

namespace MiddlewareDemo
{
    using Microsoft.AspNetCore.Http;
    using System.Threading.Tasks;
    
    //1.定義並實現業務邏輯
    public interface IMyScopedService
    {
        int MyProperty { get; set; }
    }

    public class MyScopedService : IMyScopedService
    {
        public int MyProperty { get; set; }
    }

    //2.創建中間件代理類
    public class CustomMiddleware
    {
        private readonly RequestDelegate _next;

        public CustomMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        // IMyScopedService is injected into Invoke
        public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
        {
            svc.MyProperty = 1000;
            await _next(httpContext);
        }
    }
}

//3.1 添加依賴服務注冊
namespace Microsoft.Extensions.DependencyInjection
{
    using MiddlewareDemo;
    public static partial class CustomMiddlewareExtensions
    {
        /// <summary>
        /// 添加服務的依賴注冊
        /// </summary>
        public static IServiceCollection AddCustom(this IServiceCollection services)
        {
            return services.AddScoped<IMyScopedService, MyScopedService>();
        }

    }
}

//3.2 創建中間件擴展類
namespace Microsoft.AspNetCore.Builder
{
    using MiddlewareDemo;

    public static partial class CustomMiddlewareExtensions
    {
        /// <summary>
        /// 使用中間件
        /// </summary>
        public static IApplicationBuilder UseCustom(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomMiddleware>();
        }
    }
}

//4. 使用中間件
public void ConfigureServices(IServiceCollection services)
{
    services.AddCustom();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseCustom();
}

基於工廠激活的中間件

我們前面介紹的中間件開發都是基於約定的,可以讓我們快速上手進行開發。同時ASP.NET Core還提供了基於工廠激活的中間件開發方式,我們可以通過實現IMiddlewareFactory、IMiddleware接口進行中間件開發。

public class FactoryActivatedMiddleware : IMiddleware
{
    private readonly AppDbContext _db;

    public FactoryActivatedMiddleware(AppDbContext db)
    {
        _db = db;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            _db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "FactoryActivatedMiddleware", 
                    Value = keyValue
                });

            await _db.SaveChangesAsync();
        }

        await next(context);
    }
}

上面這段代碼演示了如何使用基於工廠激活的中間件,在使用過程中有兩點需要注意:1.需要在ConfigureServices中進行服務注冊;2.在UseMiddleware()方法中不支持傳遞參數。

參考文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM