WebApi 插件式構建方案:重寫的控制器獲取工廠


模塊化的 WebApi 服務,最核心的東西就是這貨了:負責請求 URL 和控制器類型的映射 —— 簡單來說就是紅娘,不認識的話,小伙子你別想討到媳婦兒了。

系統內置的缺省 WebApi 控制器發現工廠,只能從路由信息中獲得控制器和動作,要獲取自定義的路由信息,只能通過重寫控制器獲取工廠 IHttpControllerSelector 來解決。按照第一章中定下的規則,使用 {module}/{controller}/{action}/{id} 路由規則,我們需要為路由參數額外讀取一個 module 參數。

注:id 變量並不由控制器獲取工廠使用,在實際使用中由 Action 相關類映射:一個很明顯的例子就是 WebApi 2 的 Attribute 映射。

話說回來,也不一定必須使用我定下這套規則,確保你能訪問控制器即可。在這里簡要說一下 IHttpControllerSelector 接口兩大方法的作用:

  1. GetControllerMapping 用於獲取路由規則和控制器類型的關系映射列表。
  2. 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 =&gt; 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);

}

相信用心看到這里的人,心里已經隱隱明白了寫什么。留個作業來檢驗下你的成果吧:如果要針對同一個功能,開發兩個版本,此時該如何修改呢?

提示一下:在這兩個方法里面加些東西就好了。實際並沒有標准答案,功能實現了就行,下一篇文章我會寫幾種實現,需要的拿去就好。


免責聲明!

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



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