zoukankan      html  css  js  c++  java
  • 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);
    

    }

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

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

  • 相关阅读:
    WinFrom 第三方控件 TeleRik控件
    部署项目可以将源码删掉
    XDocument简单入门
    通过HttpWebRequest在后台对WebService进行调用
    C#中HttpWebRequest的用法详解
    HttpWebRequest类
    系统之间通讯方式—SOAP(web service)
    Linux strace命令
    使用 Linux 的 strace 命令跟踪/调试程序的常用选项
    Linux umount的device is busy问题
  • 原文地址:https://www.cnblogs.com/lenic/p/4218384.html
Copyright © 2011-2022 走看看