前言:此文為極簡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序列化決議,賦值粘貼即可,看上圖)
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/116665.html
標籤:C#
下一篇:常用正則運算式
