前言
在上一篇中簡單講了一些基礎知識,例如Asp.Net Core Middleware 的使用,DI的簡單使用以及嵌入式資源的使用方法等。本篇就是結合基礎知識來構建一個基礎框架出來。
那麼框架有什麼功能呢?
- 攔截LayIM請求
- 簡單路由功能
- 路由排程器
- 通用介面
下面就基於以上四點搭建基礎框架。其他快取,日誌什麼的就先不在介紹。
攔截LayIM請求
正如上一篇介紹的那樣,實現一箇中介軟體就可以做攔截請求操作,換句話說,如果是layim的請求,我們不要放過。如果不是,那麼拜拜。但是由於我們又使用了系統的 EmbeddedFileProvider ,所以靜態資源交給系統去處理就好。這裡呢我使用一個很簡單的方式來判斷是否是LayIM的請求,就是通過請求的path字首去判斷。在 LayIMMiddleware入口方法Invoke中,通過IsLayIMRequest擴充套件方法去判斷是否是LayIM請求。程式碼如下:
/// <summary> /// 是否LayIM介面請求 /// </summary> /// <param name="context"></param> /// <param name="options"></param> /// <returns></returns> public static bool IsLayIMRequest(this HttpContext context, LayIMOptions options) { return IsConfigPath(context.Request.Path.Value) || context.Request.Path.Value.StartsWith(options.ApiPrefix, StringComparison.CurrentCultureIgnoreCase); }
沒錯,就這麼簡單粗暴,用了一個StartWith方法。程式碼中IsConfigPath以後在講。在這裡,字首可以是使用者自定義的。可以在UselayIM中傳入定義方法:
app.UseLayIM(options => { options.ApiPrefix = "/mylayim"; });
比如上文中我改成了/mylayim開頭的,測試一下。
可以看到,正常處理。
簡單路由功能
正如上文中的路徑 /mylayim/init?uid=1 是如何進行處理的呢?這裡我們的路由就要出場了。之前這段程式碼還是借鑑了Hangfire中 的程式碼實現的。它的路由很簡單,就是通過正則去匹配。不過我這裡實現的路由沒有那麼強大,為了方便,很多url都定義死了。而且不支援url中帶引數解析的情況,例如 init/{uid}.不過這個後期會考慮。路由匹配程式碼如下:
/// <summary> /// 通過path找到對應的Dispatcher /// </summary> /// <param name="path"></param> /// <returns></returns> private Tuple<ILayIMDispatcher, Match> FindDispatcherMatch(string path) { if (string.IsNullOrEmpty(path)) { path = "/"; } foreach (var dispatcher in dispatchers) { var pattern = $"^{dispatcher.Item1}$" ; var match = Regex.Match(path, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline); if (match.Success) { return new Tuple<ILayIMDispatcher, Match>(dispatcher.Item2, match); } } return null; }
沒錯就這麼一個方法實現了路由,是不是很簡單。(複雜的還沒去研究。。。。)從程式碼中我們可以看到方法返回了一個 Tuple<ILayIMDispatcher, Match> ,這個ILayIMDispatcher是何方神聖呢?讓我們進入下一節吧。
路由排程器(ILayIMDispatcher)
這個排程器翻譯的不是很準確,不過大家理解就好。如果不理解的話,看下面的圖就知道了。
介面ILayIMDispatcher裡面就一個方法
Task Dispatch(HttpContext context);
那麼他們又可以細分為多種型別,在CQRS的概念裡,我們對聚合的增刪改都屬於命令(Command),那麼我們可以定義一個CommandDispatcher,不過我這裡沒有那麼嚴格按照CQRS的方式,所以查詢我也把他歸類為查詢命令:QueryCommandDispatcher。
下面我們看一下具體程式碼:
internal class QueryCommandDispatcher<TResult> : CommandDispatcher<TResult> { protected override string AllowMethod => HttpGet; private readonly Func<HttpContext, TResult> executeFunction; public QueryCommandDispatcher(Func<HttpContext, TResult> executeFunction) { this.executeFunction = executeFunction; } }
在建構函式裡面我們傳入了一個 Func<HttpContext, TResult>,那麼這個Func就是我們的業務邏輯了。
比如在路由裡,我們新增 /layim/init 的 QueryCommandDispatcher。程式碼如下:
//layim初始化介面 routes.AddQueryCommand<object>("/init", context => { //這裡只是演示(邏輯未實現) return context.Request.Query["uid"]; });
其中AddQueryCommand是路由的一個擴充套件方法:
/// <summary> /// 註冊返回值為TResult型別的命令路由 /// </summary> /// <typeparam name="TResult">返回型別</typeparam> /// <param name="routes">當前路有集合</param> /// <param name="path">路徑</param> /// <param name="command">執行命令</param> public static void AddQueryCommand<TResult>(this RoutesCollection routes, string path, Func<HttpContext, TResult> command) { Error.ThrowIfNull(path, nameof(path)); Error.ThrowIfNull(command, nameof(command)); routes.Add(path, new QueryCommandDispatcher<TResult>(command)); }
那麼,這樣的話,路由第一步先找到相對應 /layim/init 的排程器,然後執行Dispatch方法即可,最後返回所需要的資料。正如第一節裡的截圖那個最終處理效果。
通用介面
通用介面其實在之前的文章中有講過,他的作用就是業務和框架解耦。也就是說我設計好一個通用介面,如果使用者不想使用框架的預設實現,可以自行定義實現方法,然後通過依賴注入的形式替換掉框架預設實現,這裡不在贅述。比如框架的預設實現是Dapper,那麼使用者可以自己改為EntityFramework或者其他實現。
總結
本文簡單的介紹了框架的結構和基本實現,實現較為簡單,功能相對來說比較單一,不過由於是偏向LayIM業務的,所以並沒有想把它設計的多麼複雜,功能多麼強大,而且主要是能力不夠,哈哈哈哈。
部落格預告:LayIM.AspNetCore Middleware 開發日記(四)主角登場(LayIM介紹)
專案地址:https://github.com/fanpan26/LayIM.AspNetCore (本文程式碼對應blog3分支或者直接檢視master)歡迎小夥伴們star 圍觀 提意見。