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 被實例化時,它們將被自動解析

2017年5月15日 星期一

EF6 Exception : 指定的 LocalDB 執行個體不存在

情境:
使用EF Code First,未設定web.config的connectionStrings給DbContext使用,打算使用預設行為建立,執行application欲建立 db 時發生錯誤:

建立連接至 SQL Server 時,發生網路相關或執行個體特定的錯誤。找不到或無法存取伺服器。確認執行個名稱是否正確,以及 SQL Server 是否設定為允許遠端連線。 (provider: SQL Network Interfaces, error: 50 - Local Database Runtime 發生錯誤。 指定的 LocalDB 執行個體不存在。
)


在cmd輸入 sqllocaldb i 查看我有那些 LocalDB 執行個體 :
MSSQLLocalDB
ProjectsV13

以前得知的知識,預設執行個體是 v11.0
所以以為 [指定的 LocalDB 執行個體] 指的是 v11.0
發現我沒有 v11.0 所以打算建立一個,在cmd輸入 sqllocaldb c v11.0
但是發生錯誤:
因為發生下列錯誤,建立 LocalDB 執行個體 "v11.0" 失敗:
無法在這台電腦上使用者定的 LocalDB版本。

上網查到了這一篇文章:
http://rowo.co/blog/2014/08/28/exception-with-new-ef6-project/
於是檢查自己的web.config

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v13.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

發現預設使用的是 v13.0
因為沒有這個執行個體所以建立了一個 sqllocaldb c "v13.0"
再次執行application,成功!