模塊化的 WebApi 服務,最核心的東西就是這貨了:負責請求 URL 和控制器類型的映射 —— 簡單來說就是紅娘,不認識的話,小伙子你別想討到媳婦兒了。
系統內置的缺省 WebApi 控制器發現工廠,只能從路由信息中獲得控制器和動作,要獲取自定義的路由信息,只能通過重寫控制器獲取工廠 IHttpControllerSelector
來解決。按照第一章中定下的規則,使用 {module}/{controller}/{action}/{id}
路由規則,我們需要為路由參數額外讀取一個 module 參數。
注:id 變量並不由控制器獲取工廠使用,在實際使用中由 Action 相關類映射:一個很明顯的例子就是 WebApi 2 的 Attribute 映射。
話說回來,也不一定必須使用我定下這套規則,確保你能訪問控制器即可。在這里簡要說一下 IHttpControllerSelector
接口兩大方法的作用:
-
GetControllerMapping
用於獲取路由規則和控制器類型的關系映射列表。 -
SelectController
用於根據路由規則獲取對應的控制器類型。
首先說一下 GetControllerMapping
方法:從當前加載的所有程序集中,獲取得到符合條件的所有控制器類型,然后依據 {module}/{controller}/{action}/{id}
路由規則,從請求 URL 地址中獲取對應變量的值,拼接形成緩存鍵后,添加到字典中。
根據《WebApi 插件式構建方案:發現並加載程序集》這一章定下的配置文件,是不包含 name 屬性的(即 module 路由變量),我們需要為其擴展,擴展后的結果如下(這里只考慮在基礎配置上擴展):
<?xml version="1.0" encoding="UTF-8"?>
<configuration enabled="true">
<name>Authorization</name>
<description>授權支持插件</description>
<assemblies>
<add type="relative">bin/Intime.AuthorizationService.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Services.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Data.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Data.Repository.dll</add>
</assembiles>
</configuration>
在填充路由規則和控制器類型的關系映射時,讀取 name 到路由變量 module 中,生成緩存項的鍵 Key。下面是填充邏輯真實代碼:
注:下面這段代碼,返回的字典中,鍵(string 類型)必須是不區分大小寫的,否則 Http 請求地址大小寫不同時找不到控制器。
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
<span class="pl-k">foreach</span> (<span class="pl-k">var</span> item <span class="pl-k">in</span> DynamicModule.DefaultInstance.Modules)
item.Configuration = <span class="pl-s">new</span> NamedPluginConfiguration(item.Configuration);
<span class="pl-k">var</span> types = _configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(_configuration.Services.GetAssembliesResolver());
<span class="pl-k">foreach</span> (<span class="pl-k">var</span> type <span class="pl-k">in</span> types)
{
<span class="pl-k">var</span> moduleName = <span class="pl-st">string</span>.Empty;
<span class="pl-k">var</span> module = DynamicModule.DefaultInstance.Modules.FirstOrDefault(p => p.Assemblies.Contains(type.Assembly));
<span class="pl-k">if</span> (module != <span class="pl-c1">null</span>)
moduleName = ((NamedPluginConfiguration)module.Configuration).Name;
<span class="pl-k">var</span> segments = type.Namespace.Split(Type.Delimiter);
<span class="pl-k">var</span> controllerName = type.Name.Remove(type.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
<span class="pl-k">var</span> key = String.Format(CultureInfo.InvariantCulture, <span class="pl-s1"><span class="pl-pds">"</span>{0}.{1}<span class="pl-pds">"</span></span>, segments[segments.Length - <span class="pl-c1">1</span>], controllerName);
<span class="pl-k">if</span> (!<span class="pl-st">string</span>.IsNullOrWhiteSpace(moduleName))
key = String.Format(CultureInfo.InvariantCulture, <span class="pl-s1"><span class="pl-pds">"</span>{0}.{1}<span class="pl-pds">"</span></span>, moduleName, key);
<span class="pl-k">if</span> (dictionary.Keys.Contains(key))
_duplicates.Add(key);
<span class="pl-k">else</span>
dictionary[key] = <span class="pl-s">new</span> HttpControllerDescriptor(_configuration, type.Name, type);
}
<span class="pl-k">foreach</span> (<span class="pl-k">var</span> item <span class="pl-k">in</span> _duplicates)
dictionary.Remove(item);
<span class="pl-k">return</span> dictionary;
}
需要注意的是 NamedPluginConfiguration
類:我采用了修飾模式對原始配置進行了擴展。真實代碼中,上一節的 AppConfig
也使用了這種方式,好處是:后續在有擴展需要在配置文件中添加信息時,可以很方便的讀取而不需要另開爐灶。推薦各位在配置文件的讀取上也使用這種設計模式。
上面說完了填充映射關系,下面繼續說 SelectController
方法,也就是獲取映射關系。獲取映射關系就是根據客戶端傳過來的路由變量,根據填充時的規則引擎,重新生成映射關系的鍵,找到對應的控制器,再進行下一步操作。真實代碼如下:
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
<span class="pl-k">var</span> moduleName = routeData.GetRouteVariable(ModuleKey);
<span class="pl-st">string</span> namespaceName = routeData.GetRouteVariable(NamespaceKey);
<span class="pl-k">if</span> (namespaceName == <span class="pl-c1">null</span>)
<span class="pl-k">throw</span> <span class="pl-s">new</span> HttpResponseException(HttpStatusCode.NotFound);
<span class="pl-st">string</span> controllerName = routeData.GetRouteVariable(DefaultHttpControllerSelector.ControllerSuffix);
<span class="pl-k">if</span> (controllerName == <span class="pl-c1">null</span>)
<span class="pl-k">throw</span> <span class="pl-s">new</span> HttpResponseException(HttpStatusCode.NotFound);
<span class="pl-st">string</span> key = String.Format(CultureInfo.InvariantCulture, <span class="pl-s1"><span class="pl-pds">"</span>{0}.{1}<span class="pl-pds">"</span></span>, namespaceName, controllerName);
<span class="pl-k">if</span> (!<span class="pl-st">string</span>.IsNullOrWhiteSpace(moduleName))
key = String.Format(CultureInfo.InvariantCulture, <span class="pl-s1"><span class="pl-pds">"</span>{0}.{1}<span class="pl-pds">"</span></span>, moduleName, key);
HttpControllerDescriptor controllerDescriptor;
<span class="pl-k">if</span> (_controllers.Value.TryGetValue(key, out controllerDescriptor))
<span class="pl-k">return</span> controllerDescriptor;
<span class="pl-k">if</span> (_duplicates.Contains(key))
<span class="pl-k">throw</span> <span class="pl-s">new</span> HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, <span class="pl-s1"><span class="pl-pds">"</span>有多個控制器符合這個請求!<span class="pl-pds">"</span></span>));
<span class="pl-k">else</span>
<span class="pl-k">throw</span> <span class="pl-s">new</span> HttpResponseException(HttpStatusCode.NotFound);
}
相信用心看到這里的人,心里已經隱隱明白了寫什么。留個作業來檢驗下你的成果吧:如果要針對同一個功能,開發兩個版本,此時該如何修改呢?
提示一下:在這兩個方法里面加些東西就好了。實際並沒有標准答案,功能實現了就行,下一篇文章我會寫幾種實現,需要的拿去就好。