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

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

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

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

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

  1. GetControllerMapping 用於獲取路由規則和控制器類型的關係映射列表。
  2. SelectController 用於根據路由規則獲取對應的控制器類型。

首先說一下 GetControllerMapping 方法:從當前加載的全部程序集中,獲取獲得符合條件的全部控制器類型,而後依據 {module}/{controller}/{action}/{id} 路由規則,從請求 URL 地址中獲取對應變量的值,拼接造成緩存鍵後,添加到字典中。markdown

根據《WebApi 插件式構建方案:發現並加載程序集》這一章定下的配置文件,是不包含 name 屬性的(即 module 路由變量),咱們須要爲其擴展,擴展後的結果以下(這裏只考慮在基礎配置上擴展):app

<?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 請求地址大小寫不一樣時找不到控制器。url

private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
    var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

    foreach (var item in DynamicModule.DefaultInstance.Modules)
        item.Configuration = new NamedPluginConfiguration(item.Configuration);

    var types = _configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(_configuration.Services.GetAssembliesResolver());
    foreach (var type in types)
    {
        var moduleName = string.Empty;
        var module = DynamicModule.DefaultInstance.Modules.FirstOrDefault(p => p.Assemblies.Contains(type.Assembly));
        if (module != null)
            moduleName = ((NamedPluginConfiguration)module.Configuration).Name;

        var segments = type.Namespace.Split(Type.Delimiter);
        var controllerName = type.Name.Remove(type.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

        var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName);
        if (!string.IsNullOrWhiteSpace(moduleName))
            key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", moduleName, key);

        if (dictionary.Keys.Contains(key))
            _duplicates.Add(key);
        else
            dictionary[key] = new HttpControllerDescriptor(_configuration, type.Name, type);
    }

    foreach (var item in _duplicates)
        dictionary.Remove(item);

    return dictionary;
}

須要注意的是 NamedPluginConfiguration 類:我採用了修飾模式對原始配置進行了擴展。真實代碼中,上一節的 AppConfig 也使用了這種方式,好處是:後續在有擴展須要在配置文件中添加信息時,能夠很方便的讀取而不須要另開爐竈。推薦各位在配置文件的讀取上也使用這種設計模式。spa

上面說完了填充映射關係,下面繼續說 SelectController 方法,也就是獲取映射關係。獲取映射關係就是根據客戶端傳過來的路由變量,根據填充時的規則引擎,從新生成映射關係的鍵,找到對應的控制器,再進行下一步操做。真實代碼以下:插件

public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
    IHttpRouteData routeData = request.GetRouteData();
    if (routeData == null)
        throw new HttpResponseException(HttpStatusCode.NotFound);

    var moduleName = routeData.GetRouteVariable(ModuleKey);

    string namespaceName = routeData.GetRouteVariable(NamespaceKey);
    if (namespaceName == null)
        throw new HttpResponseException(HttpStatusCode.NotFound);

    string controllerName = routeData.GetRouteVariable(DefaultHttpControllerSelector.ControllerSuffix);
    if (controllerName == null)
        throw new HttpResponseException(HttpStatusCode.NotFound);

    string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName);
    if (!string.IsNullOrWhiteSpace(moduleName))
        key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", moduleName, key);

    HttpControllerDescriptor controllerDescriptor;
    if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
        return controllerDescriptor;

    if (_duplicates.Contains(key))
        throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "有多個控制器符合這個請求!"));
    else
        throw new HttpResponseException(HttpStatusCode.NotFound);
}

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

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

相關文章
相關標籤/搜索