這篇記錄閱讀NetCore中介軟體的心得整理
NetCore的中介軟體用以處理每個元件的request和response,
不像以往asp.net實作httpModule/httpHandler相關事件來完成,
我們先複習httpModule和httpHandler的運作方式如下
From Microsoft
相關httpHandler需實作IHttpHandler,用來處理相關副檔名(如.report)的request,
簡單來說,所有httprequest最終都會由實作IhttpHandler來處理,
可透過web.config註冊httpHandler。
上圖我們可以知道,http請求httpHandler之前,需要經過一系列的httpModule,
請求之後,又需要再次返回通過一系列的httpModule,
我們透過httpModule在http的pipeline中註冊我們希望對application事件的處理邏輯,
例如一個BeginRequest事件觸發,便會調用httpModule註冊的方法(相關httpModule需實作IHttpModule),
實際的處理邏輯都在這方法中,例如上圖的驗證(Authoriztion module,當觸發一個AuthenticateRequest事件),
由於ASP.net內建相當多的httpModules,如sessionStateModule和outPutCacheModule..等,
這可讓開發人員更專注在商業邏輯。
如要開發自己的httpModule只需要繼承IHttpModule,
我們來看看elmah如何實現自己的httpModule。
https://github.com/elmah/Elmah/blob/master/src/Elmah.AspNet/ErrorFilterModule.cs#L43
IHttpModule有兩個方法:init()、Dispose()。
https://github.com/elmah/Elmah/blob/master/src/Elmah.AspNet/ErrorFilterModule.cs#L63
針對註冊過的httpModules,都註冊一個OnErrorModuleFiltering事件。
NetCore Middleware
From Microsoft
前面我們快速複習了httpModule機制,但NetCore已經沒有httpModule,
而是透過Middleware來實現相似功能,和以前httpModule最大不同的是,
Middleware沒有複雜事件,我們不用像以前asp.net必須知道要在什麼事件中,
進行處理相關應用程式邏輯。
圖中我們知道NetCore在runtime期間,middleware的執行流程,
當httprequest進來時,NetCore engine會執行第一個middleware,第一個middleware會接續執行第二個middleware,
彼此間會傳送httpcontext,直到最後一個middleware結束,
最後再依照原有call stack順序,從最後一個middleware回到第一個middleware,
這時NetCore engine就會把最終的httpcontext回應(response)給client,
一整個就像是大隊接力跑完全程,下面就來看看Middleware怎麼使用。
現在我打算要幫我的web api加入一個簡單驗證存取機制,
任何的client要呼叫web api都需要有合法的user-key或api-key,
來看看如何透過middleware實現。
新增Repository
public interface ICustomersRepository
{
void Add(CustomersModule item);
IEnumerable<CustomersModule> GetAll();
CustomersModule Find(string key);
void Remove(string Id);
void Update(CustomersModule item);
bool CheckValidUserKey(string reqkey);
}
public class CustomersRepository : ICustomersRepository
{
static List<CustomersModule> _customers = new List<CustomersModule>();
void ICustomersRepository.Add(CustomersModule item)
{
_customers.Add(item);
}
public bool CheckValidUserKey(string reqkey)
{
var userKeys = new List<string>();
userKeys.Add("123abc987plm");
userKeys.Add("098def567okn");
userKeys.Add("387bgt054edc");
userKeys.Add("327qwe000swx");
if (userKeys.Contains(reqkey))
return true;
else
return false;
}
CustomersModule ICustomersRepository.Find(string key)
{
return _customers
.Where(e => e.MobilePhone.Equals(key))
.SingleOrDefault();
}
IEnumerable<CustomersModule> ICustomersRepository.GetAll()
{
_customers.Add(new CustomersModule()
{
ID = Guid.NewGuid().ToString(),
Name = "Rico",
MobilePhone = "12345"
});
return _customers;
}
void ICustomersRepository.Remove(string Id)
{
var itemToRemove = _customers.SingleOrDefault(r => r.MobilePhone == Id);
if (itemToRemove != null)
_customers.Remove(itemToRemove);
}
void ICustomersRepository.Update(CustomersModule item)
{
var itemToUpdate = _customers.SingleOrDefault(r => r.MobilePhone == item.MobilePhone);
if (itemToUpdate != null)
{
itemToUpdate.ID = Guid.NewGuid().ToString();
itemToUpdate.Name = item.Name;
itemToUpdate.Email = item.Email;
itemToUpdate.MobilePhone = item.MobilePhone;
}
}
}
新增Middleware
public class ApiKeyValidatorsMiddleware
{
private readonly RequestDelegate _next;//we have to delegate
private ICustomersRepository _customersRepository { get; set; }//DI first
public ApiKeyValidatorsMiddleware(RequestDelegate next, ICustomersRepository _repo)
{
_next = next;
_customersRepository = _repo;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("user-key"))
{
context.Response.StatusCode = 400; //Bad Request
await context.Response.WriteAsync("User Key is missing");
return;
}
else
{
if (!_customersRepository.CheckValidUserKey(context.Request.Headers["user-key"]))
{
context.Response.StatusCode = 401; //UnAuthorized
await context.Response.WriteAsync("Invalid User Key");
return;
}
}
await _next.Invoke(context);
}
}
public static class ApiKeyValidatorsExtension
{
public static IApplicationBuilder UseApiKeyValidation(this IApplicationBuilder app)
{
app.UseMiddleware<ApiKeyValidatorsMiddleware>();
return app;
}
}
public static class RepositoryExtension
{
public static IServiceCollection SetupCustomerRepo(this IServiceCollection services)
{
services.AddSingleton<ICustomersRepository, CustomersRepository>();
return services;
}
}
必須有Invoke method,因為這是middleware的進入點,
最後再透過IApplicationBuilder和IServiceCollection的extension來擴充。
Startup.cs加入剛剛的middleware
順序很重要,所有httprequest的第一個middleware應該都要通過我們的api存取驗證才准存取相關資源。
測試結果
Header沒有帶User key。
非法key
合法key即可存取相關資源。
參考
Migrating HTTP handlers and modules to ASP.NET Core middleware
Custom ASP.NET Core Middleware Example
ASP.NET Core Middleware in WebAPI
Create Your Own ASP.NET Core Middleware