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);

}

相信用心看到这里的人,心里已经隐隐明白了写什么。留个作业来检验下你的成果吧:如果要针对同一个功能,开发两个版本,此时该如何修改呢?

提示一下:在这两个方法里面加些东西就好了。实际并没有标准答案,功能实现了就行,下一篇文章我会写几种实现,需要的拿去就好。

posted @ 2015-01-12 15:03  Lenic  阅读(2460)  评论(12编辑  收藏  举报