2017年5月18日 星期四

在 ASP.NET Core 自定義中間件(middleware)使用依賴注入(DI)

很多ASP.NET Core專案可能需要自定義 middleware,所以筆記一下用簡單的方法自製一個middleware 並用內建的 dependency injection 系統

使用  ASP.NET Core 1.1


ASP.NET Core middleware  方法被定義在Startup類別的Configure方法裡,當應用程式啟動的時候,框架會自動執行這個方法,並提供IApplicationBuilder實例,使用該實例的Use方法註冊自定義 middleware
IApplicationBuilder Use(Func<RequestDelegateRequestDelegate> middleware);
Use方法接受一個Func參數,該參數接受和返回都是 RequestDelegate 
public delegate Task RequestDelegate(HttpContext context)
[範例 1]:
app.Use(next =>
{
    return async context =>
    {
        await context.Response.WriteAsync("Hello World!");
        await next(context);
    };
});
用 lambda 表示 Func<RequestDelegateRequestDelegate>,參數 next 是下一個middle;return的是一個新的 RequestDelegate,他接收 HttpContext 實例  

除了[範例 1]以外,同個命名空間(Microsoft.AspNetCore.Builder)下有一個擴展方法寫法較為簡短
[範例 2]:
app.Use(async (ctx, next) =>
{
    await ctx.Response.WriteAsync("Hello World!");
    await next();
});
他接收的參數類型:
Func<HttpContextFunc<Task>, Task>
如果是最後一個middleware, 可以使用 Run(就像Use沒用next)
app.Run(async context => await context.Response.WriteAsync("Hi"));

另一個建立middleware的方式允許在class裡封裝邏輯,該class必須有接受HttpContext的Invoke方法:
public class MyMiddleware
{
    private readonly RequestDelegate _next;
 
    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }
 
    public async Task Invoke(HttpContext context)
    {
        await context.Response.WriteAsync("Hi!");
        await _next(context);
    }
}
該方法必須有一個接受RequestDelegate實例的建構式,可以在Invoke方法裡去呼叫下一個middleware。

使用自定義middleware的方式:
app.UseMiddleware<MyMiddleware>();
或是擴展
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
{
    app.UseMiddleware<MyMiddleware>();
    return app;
}
然後使用他
app.UseMyMiddleware();

如果middleware需要呼叫另一個物件的方法,可以在middleware裡面建立物件,更優雅的寫法是使用內建的dependency injection

Startup類別的ConfigureServices方法可以註冊依賴物件,該方法比Configure早執行,所以所有的依賴物件可以被middleware使用

建立interface和class:
public interface IGreeter
{
    string Greet();
}
public class Greeter : IGreeter
{
    public string Greet()
    {
        return "Hi by Greeter!";
    }
}
在ConfigureServices方法裡面註冊:
services.AddTransient<IGreeterGreeter>();
AddTransient方法在每次request時都會建立一個實例,其他還有AddSingleton/AddScoped或是簡單的Add,整個DI系統的描述在docs.asp.net/dependency-injection 

完成註冊後,直接在 Configure 方法使用:
app.Use(async (context, next) =>
{
    IGreeter greeter = context.RequestServices.GetService<IGreeter>();
    await context.Response.WriteAsync(greeter.Greet());
    await next();
});
或是寫在自定義middleware:
public class MyMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IGreeter _greeter;
 
        public MyMiddleware(RequestDelegate next, IGreeter greeter)
        {
            _next = next;
            _greeter = greeter;
        }
 
        public async Task Invoke(HttpContext context)
        {
            await context.Response.WriteAsync(_greeter.Greet());
            await _next(context);
        }
    }
如果DI系統知道這些參數的類型,則在 class 被實例化時,它們將被自動解析

沒有留言: