C#反射與特性(六):設計一個仿ASP.NETCore依賴注入Web


【微信平台,此文僅授權《NCC 開源社區》訂閱號發布】

從前面第四篇開始,進入了實踐練習;第五篇實現了實例化一個類型以及對成員方法等的調用。當然,還有一些操作尚將在后面的章節進行介紹。

因為本系列屬於實踐練習,所以系列文章可能比較多,內容比較長。要學會一種技術,最好的方法是跟着例子代碼寫一次,運行調試。

本篇文章屬於階段練習,將前面學習到的所有知識點進行總結,實現一個依賴注入功能,仿照 ASP.NET Core 訪問 API,自動傳遞參數以及執行方法,最后返回結果。

本章的代碼已上傳至 https://gitee.com/whuanle/reflection_and_properties/blob/master/C%23反射與特性(6)設計一個仿ASP.NET Core依賴注入框架.cs


效果:

對用戶效果

  • 用戶能夠訪問 Controller
  • 用戶能夠訪問 Action
  • 訪問 Action 時,傳遞參數

程序要求效果

  • 實例化類型
  • 識別類型構造函數類型
  • 根據構造函數類型動態實例化類型並且注入
  • 動態調用合適的重載方法

1,編寫依賴注入框架

寫完后的代碼大概是這樣的

筆者直接在 Program 類里面寫了,代碼量為 200 行左右(包括詳細注釋、空白隔行)。

開始編寫代碼前,請先引入以下命名空間:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

在 Program 中,增加以下代碼

        private static Assembly assembly = Assembly.GetCallingAssembly();
        private static Type[] types;
        static Program()
        {
            types = assembly.GetTypes();
        }

上面代碼的作用是,獲取到當前程序的程序集,並且獲取元數據信息。

這是反射第一步

1.1 路由索引

ASP.NET Core 中的路由規則十分豐富,我們自定義各種 URL 規則。主要原理是程序在運行時,將 Controller 、Action 的 [route] 等特性收集起來,生成路由表。

程序執行的基礎是類型、方法,ASP.NET Core 中的 Controller 即是 Class,Action 即 Method。

從前面的學習中,我們了解到,通過反射實例化和調用一個類型的成員,只需要確定類型名稱、方法名稱即可。

對於路由表,我們可以假設(不是指ASP.NET Core的原理)用戶訪問 URL 時,先從路由表中對比,如果有結果,則將對應的 Class 、Method 拿到手,通過反射機制調用實例化類型調用函數。

這里不實現這么復雜的結構,只實現 Controller-Action 層次的路由。

1.1.1 判斷控制器 Controller 是否存在

Program 中,添加一個方法,用於判斷當前程序集中是否存在此控制器。

        /// <summary>
        /// 判斷是否有此控制器,並且返回 Type
        /// </summary>
        /// <param name="controllerName">控制器名稱(不帶Controller)</param>
        /// <returns></returns>
        private static (bool, Type) IsHasController(string controllerName)
        {
            // 不分大小寫

            string name = controllerName + "Controller";
            if (!types.Any(x => x.Name.ToLower() == name.ToLower()))
                return (false, null);

            return (true, types.FirstOrDefault(x => x.Name.ToLower() == name.ToLower()));
        }

代碼非常簡單,而且有 Linq 的加持,幾行代碼就 OK。

實現原理:

判斷程序集中是否具有 {var}Controller 命名的類型,例如 HomeController

如果存在,則獲取此控制器的 Type 。

1.1.2 判斷 Action 是否存在

Action 是在 Controller 里面的(方法在類型里面),所以我們這里只需要判斷以下就行。

        /// <summary>
        /// 判斷一個控制器中是否具有此方法
        /// </summary>
        /// <param name="type">控制器類型</param>
        /// <param name="actionName">Action名稱</param>
        /// <returns></returns>
        private static bool IsHasAction(Type type, string actionName)
        {
            // 不分大小寫

            return type.GetMethods().Any(x => x.Name.ToLower() == actionName.ToLower());
        }

實現原理:

判斷一個類型中,是否存在 {actionname} 這個方法。

這里不返回 MethodInfo,而是返回 bool ,是因為考慮到,方法是可以重載的,我們要根據請求時的參數,確定使用哪個方法。

所以這里只做判斷,獲取 MethodInfo 的過程在后面。

1.2 依賴實例化

意思是,獲取一個類型的構造函數中,所有參數信息,並且為每一個類型實現自動創建實例。

傳入參數:

需要進行依賴注入的類型的 Type。

返回數據:

構造函數參數的實例對象列表(反射都是object)。

        /// <summary>
        /// 實例化依賴
        /// </summary>
        /// <param name="type">要被實例化依賴注入的類型</param>
        public static object[] CreateType(Type type)
        {
            // 這里只使用一個構造函數
            ConstructorInfo construct = type.GetConstructors().FirstOrDefault();

            // 獲取類型的構造函數參數
            ParameterInfo[] paramList = construct.GetParameters();

            // 依賴注入的對象列表
            List<object> objectList = new List<object>();

            // 為構造函數的每個參數類型,實例化一個類型
            foreach (ParameterInfo item in paramList)
            {
                //獲取參數類型:item.ParameterType.Name

                // 獲取程序中,哪個類型實現了 item 的接口

                Type who = types.FirstOrDefault(x => x.GetInterfaces().Any(z => z.Name == item.ParameterType.Name));

                // 實例化
                object create = Activator.CreateInstance(who, new object[] { });
                objectList.Add(create);
            }
            return objectList.ToArray();
        }

這里有兩個點:

① 對於一個類型來說,可能有多個構造函數;

② 使用 ASP.NET Core 編寫一個控制器時,估計沒誰會寫兩個構造函數吧。。。

基於以上兩點,我們只要一個構造函數就行,不需要考慮很多情況,我們默認:一個控制器只允許定義一個構造函數,不能定義多個構造函數。

過程實現原理:

獲取到構造函數后,接着獲取構造函數中的參數列表(ParameterInfo[])。

這里又有幾個問題

  • 參數是接口類型

  • 參數是抽象類型

  • 參數是正常的 Class 類型

那么,按照以上划分,要考慮的情況更加多了。這里我們根據依賴倒置原則,我們約定,構造函數中的類型,只允許是接口。

因為這里沒有 IOC 容器,只是簡單的反射實現,所以我們不需要考慮那么多情況(200行代碼還想怎么樣。。。)。

后面我們查找有哪個類型實現了此接口,就把這個類型實例化做參數傳遞進去。

注:后面會持續推出更多實戰型教程,敬請期待;可以關注微信訂閱號 《NCC 開源社區》,獲取最新資訊。

1.3 實例化類型、依賴注入、調用方法

目前來到了依賴注入的最后階段,實例化一個類型、注入依賴、調用方法。

        /// <summary>
        /// 實現依賴注入、調用方法
        /// </summary>
        /// <param name="type">類型</param>
        /// <param name="actionName">方法名稱</param>
        /// <param name="paramList">調用方法的參數列表</param>
        /// <returns></returns>
        private static object StartASPNETCORE(Type type, string actionName, params object[] paramList)
        {
            // 獲取 Action 重載方法 
            // 名字一樣,參數個數一致
            MethodInfo method = type.GetMethods()
                .FirstOrDefault(x => x.Name.ToLower() == actionName.ToLower()
                && x.GetParameters().Length == paramList.Length);

            // 參數有問題,找不到合適的 Action 重載進行調用
            // 報 405 
            if (method == null)
                return "405";

            // 實例化控制器

            // 獲取依賴對象
            object[] inject = CreateType(type);
            // 注入依賴,實例化對象
            object example = Activator.CreateInstance(type, inject);

            // 執行方法並且返回執行結果
            object result;
            try
            {
                result = method.Invoke(example, paramList);
                return result;
            }
            catch
            {
                // 報 500
                result = "500";
                return result;
            }
        }

實現原理:

通過 CreateType 方法,已經拿到實例化類型的構造函數的參數對象了。

這里確定調用哪個重載方法的方式,是通過參數的多少,因為這里控制台輸入只能獲取 string,更加復雜通過參數類型獲取重載方法,可以自行另外測試。

調用一個方法大概以下幾個步驟(不分順序):

獲取類型實例;

獲取類型 Type;

獲取方法 MethodInfo;

方法的參數對象;

            // 獲取依賴對象
            object[] inject = CreateType(type);
            // 注入依賴,實例化對象
            object example = Activator.CreateInstance(type, inject);

上面代碼中,就是實現非常簡單的依賴注入過程。

剩下的就是調用方法,通過參數多少去調用相應的重載方法了。

2,編寫控制器和參數類型

2.1 編寫類型

編寫一個接口

    /// <summary>
    /// 接口
    /// </summary>
    public interface ITest
    {
        string Add(string a, string b);
    }

實現接口

    /// <summary>
    /// 實現
    /// </summary>
    public class Test : ITest
    {
        public string Add(string a, string b)
        {
            Console.WriteLine("Add方法被執行");
            return a + b;
        }
    }

2.2 實現控制器

我們按照 ASP.NET Core 寫一個控制器的大概形式,實現一個低仿的山寨控制器。

    /// <summary>
    /// 需要自動實例化並且進行依賴注入的類
    /// </summary>
    public class MyClassController
    {
        private ITest _test;
        public MyClassController(ITest test)
        {
            _test = test;
        }
        
        /// <summary>
        /// 這是一個 Action
        /// </summary>
        /// <returns></returns>
        public string Action(string a, string b)
        {
            // 校驗http請求的參數
            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b))
                return "驗證不通過";
            //開始運行
            var result = _test.Add(a, b);
            Console.WriteLine("NCC社區", "牛逼");
            // 響應結果
            return result;
        }
    }

這是常見的依賴注入使用場景:

        private ITest _test;
        public MyClassController(ITest test)
        {
            _test = test;
        }

可以是一個數據庫上下文,可以各種類型。

由於控制台輸入獲取到的是 string,為了減少麻煩,里面只使用的 Action 方法,參數類型都是 string

3,實現低配山寨 ASP.NET Core

好吧,我承認我這跟ASP.NET Core沒關系,這個這是一個非常簡單的功能。

主要就是仿照 StartUp ,實現請求流程和數據返回。

        static void Main(string[] args)
        {

            while (true)
            {
                string read = string.Empty;
                Console.WriteLine("用戶你好,你要訪問的控制器(不需要帶Controller)");

                read = Console.ReadLine();

                // 檢查是否具有此控制器並且獲取 Type

                var hasController = IsHasController(read);

                // 找不到控制器,報 404 ,讓用戶重新請求
                if (!hasController.Item1)
                {
                    Console.WriteLine("404");
                    continue;
                }

                Console.WriteLine("控制器存在,請接着輸入要訪問的 Action");

                read = Console.ReadLine();

                // 檢查是否具有此 Action 並且獲取 Type
                bool hasAction = IsHasAction(hasController.Item2, read);

                // 找不到,繼續報 404 
                if (hasAction == false)
                {
                    Console.WriteLine("404");
                    continue;
                }

                // 目前為止,URL存在,那么就是傳遞參數了

                Console.WriteLine("用戶你好,URL 存在,請輸入參數");
                Console.WriteLine("輸入每個參數按一下回車鍵,結束輸入請輸入0再按下回車鍵");

                // 開始接收用戶輸入的參數
                List<object> paramList = new List<object>();
                while (true)
                {
                    string param = Console.ReadLine();
                    if (param == "0")
                        break;
                    paramList.Add(param);
                }

                Console.WriteLine("輸入結束,正在發送 http 請求 \n");

                // 用戶的請求已經校驗通過並且開始,現在來繼續仿 ASP.NET Core 執行

                object response = StartASPNETCORE(hasController.Item2, read, paramList.ToArray());

                Console.WriteLine("執行結果是:");
                Console.WriteLine(response);


                Console.ReadKey();
            }

實現過程和原理:

  • 判斷 URL 是否存在(路由)
  • 接收用戶輸入的參數
  • 依賴注入實現
  • 調用方法,傳輸參數,返回實現結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM