[netCore] MiddleWare

這篇記錄閱讀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即可存取相關資源。

 

參考

ASP.NET Core Middleware

Migrating HTTP handlers and modules to ASP.NET Core middleware

ASP.NET Core 的 Middleware

Custom ASP.NET Core Middleware Example

ASP.NET Core Middleware in WebAPI

Create Your Own ASP.NET Core Middleware