【微信平台,此文僅授權《NCC 開源社區》訂閱號發布】
從前面第四篇開始,進入了實踐練習;第五篇實現了實例化一個類型以及對成員方法等的調用。當然,還有一些操作尚將在后面的章節進行介紹。
因為本系列屬於實踐練習,所以系列文章可能比較多,內容比較長。要學會一種技術,最好的方法是跟着例子代碼寫一次,運行調試。
本篇文章屬於階段練習,將前面學習到的所有知識點進行總結,實現一個依賴注入功能,仿照 ASP.NET Core 訪問 API,自動傳遞參數以及執行方法,最后返回結果。

效果:
對用戶效果
- 用戶能夠訪問 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 是否存在(路由)
- 接收用戶輸入的參數
- 依賴注入實現
- 調用方法,傳輸參數,返回實現結果