白話系列之實現自己簡單的mvc式webapi框架

来源:https://www.cnblogs.com/yichaohong/archive/2019/10/22/11579598.html
-Advertisement-
Play Games

前言:此文為極簡mvc式的api框架,只當做入門api的解析方式,並且這裡也不算是mvc框架,因為沒有view層,畢竟現在大部分都屬於前後端分離,當然也可以提供view層,因為只是將view當做文本返回. github地址:https://github.com/BestHYC/WebAPISolut ...


前言:此文為極簡mvc式的api框架,只當做入門api的解析方式,並且這裡也不算是mvc框架,因為沒有view層,畢竟現在大部分都屬於前後端分離,當然也可以提供view層,因為只是將view當做文本返回.

github地址:https://github.com/BestHYC/WebAPISolution.git

演示例子:

 

 

 

目標:

1.針對Home/default進行解析.2.提供簡單的httpcontext處理.3.對mvc的框架有個最簡單的瞭解.4.一步步通過好的框架風格,建立自己的框架風格

關鍵還有一點就是很多東西比你想的還簡單,難得是裡面可擴展可維護的實現方式以及面面俱到的安全驗證.但是前人栽樹後人乘涼,我們借鑒就好.

一:創建controller對象

目標:通過工廠模式,獲取所有controller,並創建其對象

1.1.定義介面,並定義所有Controller繼承的基類ApiBaseController

    public interface IApiController
    {
    }
    public abstract class ApiBaseController : IApiController
    {
    }

  模擬創建一個Controller,這就是我們自己定義的控制器,其實就是你自己實現的控制器

    public class HomeController : ApiBaseController
    {
    }

1.2.通過工廠模式,通過名稱調用對應的控制對象,因為控制器基本上不會改變,然後通過name找到對應的控制器

   public interface IControllerFactory
    {
        IApiController CreateController(String name);
    }
    public class DefaultControllerFactory : IControllerFactory
    {
        private static List<Type> controllerTypes = new List<Type>();
        static DefaultControllerFactory()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (Type type in assembly.GetTypes().Where(type => typeof(IApiController).IsAssignableFrom(type)))
            {
                controllerTypes.Add(type);
            }
        }
        public IApiController CreateController(String name)
        {
            if (name.IndexOf("Controller") == -1) name += "Controller";
            Type controllerType = controllerTypes.FirstOrDefault(c => String.Compare(name, c.Name, true) == 0);
            if (controllerType == null)
            {
                return null;
            }
            return (IApiController)Activator.CreateInstance(controllerType);
        }
    }

ok,這樣就可以取個簡單的控制器工廠模式.

二:既然控制器已經創建,那麼同樣的情況調用裡面的方法,目前home/default,會直接解析成default方法

1.先簡單的實現出調用方法

 

    public interface ActionInvoker
    {
        void InvokeAction(Object type, String actionName);
    }
    public class DefaultActionInvoker : ActionInvoker
    {
        public void InvokeAction(Object controller, String actionName)
        {
            MethodInfo methodInfo = controller.GetType().GetMethods().First(m => String.Compare(actionName, m.Name, true) == 0);
            methodInfo.Invoke(controller, null);
        }
    }

 

此處非常簡單,就直接調用對應的方法名即可,這就是webapi在解析路由中出現的最簡單的實現方式,

其實理論就是如此簡單,沒其他人想的那麼困難,接下來開始會做修飾,一步步來構建一個也是簡單,但是稍微有點健全的api

三:優化代碼,針對控制器及方法做緩存

/// <summary>
    /// 全局所有IApiController類型的操作都是由此處進行緩存
    /// 其他地方只做類型處理,比如 A/B,那麼是對應的是AController 還是A,都是其他地方做處理
    /// 註意此處,只當做類型及方法的緩存,不做任何對執行返回結果及傳遞對象的處理,保持功能單一
    /// 保持路徑單一,即A/B中A控制器只有1個,B方法也只有1個,即使有重載,也必須得通過 路由名 進行區分
    /// </summary>
    internal static class ApiControllerActionCache
    {
        private static Dictionary<String, Type> s_controllerTypes = new Dictionary<string, Type>();
        private static Dictionary<Type, ActionCache> s_actionCache = new Dictionary<Type, ActionCache>();
        static ApiControllerActionCache()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (Type type in assembly.GetTypes().Where(type => typeof(IApiController).IsAssignableFrom(type)))
            {
                String name = type.Name;
                if(type.GetCustomAttribute<PreRouteAttribute>() != null)
                {
                    name = type.GetCustomAttribute<PreRouteAttribute>().PreRouteName;
                }
                if (s_controllerTypes.ContainsKey(name)) throw new Exception($"{name}存在相同的路由名,請保持路由唯一");
                s_controllerTypes.Add(name, type);
                s_actionCache.Add(type, new ActionCache(type));
            }
        }
        public static Type GetController(String controllername)
        {
            if (!s_controllerTypes.ContainsKey(controllername)) throw new Exception("沒有此路由對應的類型");
            return s_controllerTypes[controllername];
        }
        /// <summary>
        /// 通過路由值獲取對應的委托方法
        /// </summary>
        /// <param name="controllername"></param>
        /// <param name="actionname"></param>
        /// <returns></returns>
        public static Func<IApiController, Object[], Object> GetMethod(Type controller, String actionname)
        {
            if(!s_actionCache.ContainsKey(controller)) throw new Exception("沒有此路由對應的類型");
            ActionCache cache = s_actionCache[controller];
            if (!cache.ContainsKey(actionname)) throw new Exception("沒有此路由對應的方法");
            return cache.GetMethodInfo(actionname);
        }
    }
    public class ActionCache
    {
        private Dictionary<String, MethodInfo> m_methodinfo;
        private Dictionary<MethodInfo, Func<IApiController, Object[], Object>> m_FuncCache ;
        public MethodInfo this[String name]
        {
            get
            {
                return m_methodinfo[name];
            }
        }
        public Boolean ContainsKey(String name)
        {
            return m_methodinfo.ContainsKey(name);
        }
        private Object m_lock = new Object();
        /// <summary>
        /// 可以考慮延遲載入
        /// </summary>
        /// <param name="type"></param>
        public ActionCache(Type type)
        {
            m_methodinfo = new Dictionary<String, MethodInfo>();
            m_FuncCache = new Dictionary<MethodInfo, Func<IApiController, object[], object>>();
            foreach(MethodInfo info in type.GetMethods())
            {
                String name = info.Name;
                if(info.GetCustomAttribute<RouteAttribute>() != null)
                {
                    name = info.GetCustomAttribute<RouteAttribute>().RouteName;
                }
                if (m_methodinfo.ContainsKey(name)) throw new Exception($"{type.Name}中{name}重覆,請保持路徑唯一");
                m_methodinfo.Add(name, info);
            }
        }
        /// <summary>
        /// 通過名稱獲取對應的委托
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <returns>IApiController:傳遞的執行方法, Object[]:方法參數, Object 返回值,void為空</returns>
        public Func<IApiController, Object[], Object> GetMethodInfo(String methodName)
        {
            MethodInfo methodInfo = m_methodinfo[methodName];
            if (!m_FuncCache.ContainsKey(methodInfo))
            {
                lock (m_lock)
                {
                    if (!m_FuncCache.ContainsKey(methodInfo))
                    {
                        m_FuncCache.Add(methodInfo, CreateExecutor(methodInfo));
                    }
                }
            }
            return m_FuncCache[methodInfo];
        }
        private Func<Object, Object[], Object> CreateExecutor(MethodInfo methodInfo)
        {
            ParameterExpression target = Expression.Parameter(typeof(Object), "target");
            ParameterExpression arguments = Expression.Parameter(typeof(Object[]), "arguments");
            List<Expression> parameters = new List<Expression>();
            ParameterInfo[] paramInfos = methodInfo.GetParameters();
            for (Int32 i = 0; i < paramInfos.Length; i++)
            {
                ParameterInfo paramInfo = paramInfos[i];
                BinaryExpression getElementByIndex = Expression.ArrayIndex(arguments, Expression.Constant(i));
                UnaryExpression converToParamterType = Expression.Convert(getElementByIndex, paramInfo.ParameterType);
                parameters.Add(converToParamterType);
            }
            UnaryExpression instanceCast = Expression.Convert(target, methodInfo.ReflectedType);
            MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameters);
            UnaryExpression converToObjectType = Expression.Convert(methodCall, typeof(Object));
            return Expression.Lambda<Func<Object, Object[], Object>>(converToObjectType, target, arguments).Compile();
        }
    }
View Code

 註意:此處對全局controller做了一次緩存,限製為不會通過傳的參數進行判定使用哪個方法,只允許單個介面存在,即

1.如果在不同空間下具有相同類型名的,必須具有不同的preroute特性限定,

2.如果一個類方法重載,得通過route特性限定唯一標識

3.表達式樹是通過創建一個委托,傳遞當前的controller對象調用對應的方法

以上並不算框架,只屬於單一調用方法功能實現,並做優化,接下來屬於API框架實現

四:API實現首先得確定傳輸的值及協議標準,

4.1.確定傳輸中的必須信息,去掉其他所有的額外信息後有如下幾點,為求簡單,先全部保存成字元串形式:

從哪裡來(URlReferrer),到哪裡去 URL(請求的介面),

URL請求的參數(a/b?query=1中的query值解析),body中保存的值(frombody),包含的請求頭參數(Headers)

所有請求處理完成後,返回的值信息

 

public class HttpRequest
    {
        /// <summary>
        /// 從哪裡來的
        /// </summary>
        public String UrlReferrer { get; }
        /// <summary>
        /// 到哪裡去
        /// </summary>
        public String Uri { get; set; }
        /// <summary>
        /// Uri請求參數處理
        /// </summary>
        public String QueryParams { get; set; }
        /// <summary>
        /// 請求的內容
        /// </summary>
        public String RequestContent { get; set; }
        /// <summary>
        /// 請求頭參數
        /// </summary>
        public String Headers { get; set; }
    }
    public class HttpResponse
    {
        /// <summary>
        /// 返回的內容
        /// </summary>
        public String ResponseContent { get; set; }
    }
    public class HttpContext
    {
        public HttpRequest Request { get; }
        public HttpResponse Response { get; }
    }
View Code

 

此處只是單純做展示使用,在後期會寫一個MQ中間件時候,在做擴展.只展示HttpContext

4.2.對Http請求做一個統一介面處理

    public class UrlRoutingModule : IRoutingModule
    {
        public void Init(HttpBaseContext context)
        {
            
        }
    }

五:通過路由模板收集

5.1.在寫API的時候,會添加一個defaultapi匹配路由,註意:這裡以MVC的解析規則實現

    
 RouteConfig.RegisterRoutes(RouteTable.Routes);
public static class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }

5.2.考慮此模板的功能及實現方式

1.因為可能有多個預設匹配,並且執行的是順序載入過程,所以有個RouteTable靜態類收集全局模板路由

2.路由有對應的預設路由及路由值(Route RouteData)及收集Collection

3.Route實現路由解析,並提供RouteData值,並提供實現

4.在解析Route的時候,通過生成的路由句柄DefaultRouterHandler去RouteData進行後續處理

5.通過DefaultApiHandler對路由進行解析,生成對應的控制器

6.通過controller,對當前的action進行解析,並綁定路由

7.調取當前執行的方法後,獲取返回的值對象,並對返回值進行處理

大體的模型調用過程如圖

 

 6:實現代碼:請查看對應的github地址

https://github.com/BestHYC/WebAPISolution.git

7.例子:(已經準備好對應的json序列化解析,賦值粘貼即可,看上圖)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 線程封閉 在多線程的環境中,我們經常使用鎖來保證線程的安全,但是對於每個線程都要用的資源使用鎖的話那麼程式執行的效率就會受到影響,這個時候可以把這些資源變成線程封閉的形式。 1、棧封閉 所謂的棧封閉其實就是使用局部變數存放資源,我們知道局部變數在記憶體中是存放在虛擬機棧中,而棧又是每個線程私有獨立的, ...
  • 1.題目: 企業發放的獎金根據利潤提成。利潤(I)低於或等於10萬元時,獎金可提10%; 利潤高於10萬元,低於20萬元時,低於10萬元的部分按10%提成,高於10萬元的部分,可提成7.5%; 20萬到40萬之間時,高於20萬元的部分,可提成5%;40萬到60萬之間時高於40萬元的部分,可提成3%; ...
  • GitHub Page: "http://blog.cloudli.top/posts/Java ThreadLocal 的使用與源碼解析/" 主要解決的是每個線程綁定自己的值,可以將 看成全局存放數據的盒子,盒子中存儲每個線程的私有數據。 驗證線程變數的隔離性 get() 方法 方法首先得到當前線 ...
  • 什麼是ngnix? Nginx是一個http伺服器。是一個使用c語言開發的高性能的http 伺服器/反向代理伺服器及電子郵件(IMAP/POP3)代理伺服器。nginx能夠支撐5萬併發鏈接,並且cpu、記憶體等資源消耗卻非常低,運行非常穩定,中國大陸使用nginx網站用戶有:百度、京東、新浪、網易、騰 ...
  • 併發安全 【1】什麼是類的線程安全? ​ 當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些線程將如何交替執行,並且在調用代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那麼就稱這個類是線程安全的。 【2】線程不安全引發的問題 死鎖 死鎖是指兩個或兩個以上的進程在執行過程 ...
  • 俗話說的好,千里之行始於足下。無論做什麼事情,基礎都是最重要的,當你以為自己“精通”某語言的時候,有沒有問過自己: “能不能把這些知識,用最簡單的話說出來,讓不懂的人也能聽明白?” 當你真正精通某語言的時候,我相信你一定能做到。如果做不到,那就需要往下看,再學習一下基礎。 為了幫助大家徹底理解Pyt ...
  • 直接使用docker拉取redis的鏡像,並且進行埠映射與文件目錄共用,這樣可以直接在宿主機的埠上就可以進行訪問了。其實本質上也是在一個簡化版的ubuntu的容器內安裝好的redis-server服務。 將docker修改為163鏡像源在/etc/docker/daemon.json文件中添加下 ...
  • 這是一個.Net Core API搭建的後臺架構,也是我完成公司系統重構後,重新寫的一個學習案例。寫這篇博文是想看看自己是否真的掌握了,另外也希望對讀者有一定的幫助。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...