[ASP.NET Web API]如何Host定义在独立程序集中的Controller

简介:
通过《 ASP.NET Web API的Controller是如何被创建的?》的介绍我们知道默认ASP.NET Web API在Self Host寄宿模式下用于解析程序集的AssembliesResolver是一个DefaultAssembliesResolver对象,它只会提供 当前应用程序域已经加载的程序集。如果我们将HttpController定义在非寄宿程序所在的程序集中(实际上在采用Self Host寄宿模式下,我们基本上都会选择在独立的项目定义HttpController类型),即使我们将它们部属在宿主程序运行的目录中,宿主程序启动的时候也不会主动去加载这些程序集。由于当前应用程序域中并不曾加载这些程序集,HttpController类型解析将会失败,HttpController的激活自然就无法实现。[本文已经同步到《 How ASP.NET Web API Works?》]

我们可以通过一个简单的实例来证实这个问题。我们在一个解决方案中定义了如右图所示的4个项目,其中Foo、Bar和Baz为类库项目,相应的HttpController类型就定义在这3个项目之中。Hosting是一个作为宿主的控制台程序,它具有对上述3个项目的引用。我们分别在项目Foo、Bar和Baz中定义了三个继承自ApiController的HttpController类型FooController、BarController和BazController。如下面的代码片断所示,我们在这3个HttpController类型中定义了唯一的Action方法Get并让它返回当前HttpController类型的AssemblyQualifiedName。

   1: public class FooController : ApiController
   2: {
   3:     public string Get()
   4:     {
   5:         return this.GetType().AssemblyQualifiedName;
   6:     }
   7: }
   8:  
   9: public class BarController : ApiController
  10: {
  11:     public string Get()
  12:     {
  13:         return this.GetType().AssemblyQualifiedName;
  14:     }
  15: }
  16:  
  17: public class BarController : ApiController
  18: {
  19:     public string Get()
  20:     {
  21:         return this.GetType().AssemblyQualifiedName;
  22:     }
  23: }

我们在作为宿主的Hosting程序中利用如下的代码以Self Host模式实现了针对Web API的寄宿。我们针对基地址“http://127.0.0.1:3721”创建了一个HttpSelfHostServer,在开启之前我们注册了一个URL模板为“api/{controller}/{id}”的路由。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Uri baseAddress = new Uri("http://127.0.0.1:3721");
   6:         using (HttpSelfHostServer httpServer = new HttpSelfHostServer(new HttpSelfHostConfiguration(baseAddress)))
   7:         {
   8:             httpServer.Configuration.Routes.MapHttpRoute(
   9:                 name             : "DefaultApi",
  10:                 routeTemplate    : "api/{controller}/{id}",
  11:                 defaults         : new { id = RouteParameter.Optional });
  12:  
  13:             httpServer.OpenAsync().Wait();
  14:             Console.Read();
  15:         }
  16:     }
  17: }

在启动宿主程序后,我们试图通过浏览器对分别定义在FooController、BarController和BazController中的Action方法Get发起调用,不幸的是我们会得到如图4-4所示的结果。从显示在浏览器中的消息我们很清楚问题的症结所在:根据路由解析得到HttpController名称并不能得到匹配的类型。

导致上述这个问题的原因我们在上面已经分析过了:默认注册的DefaultAssembliesResolver仅仅提供当前应用程序域加载的程序集。我们可以通过自定义的AssembliesResolver来解决这个问题。我们的解决思路是让需要预先加载的程序集可配置,具体来说可以采用具有如下结构的配置来设置需要预先加载的程序集。

   1: <configuration>
   2:    <configSections>
   3:      <section name="preLoadedAssemblies" 
   4:               type="Hosting.PreLoadedAssembliesSettings, Hosting"/>
   5:    </configSections>
   6: <preLoadedAssemblies>
   7:   <add assemblyName ="Foo.dll"/>
   8:   <add assemblyName ="Bar.dll"/>
   9:   <add assemblyName ="Baz.dll"/>
  10: </preLoadedAssemblies>
  11: </configuration>

在创建自定义的AssembliesResolver之前我们先得为这段配置定义相应的配置节和配置元素类型。相关的类型(PreLoadedAssembliesSettings、AssemblyElementCollection和AssemblyElement)定义如下所示,由于配置结构比较简单,在这里我们不对它们作详细介绍了。

   1: public class PreLoadedAssembliesSettings: ConfigurationSection
   2: {
   3:     [ConfigurationProperty("", IsDefaultCollection = true)]
   4:     public AssemblyElementCollection AssemblyNames
   5:     {
   6:         get { return (AssemblyElementCollection)this[""]; }
   7:     }
   8:  
   9:     public static PreLoadedAssembliesSettings GetSection()
  10:     {
  11:         return ConfigurationManager.GetSection("preLoadedAssemblies") 
  12:             as PreLoadedAssembliesSettings;
  13:     }
  14: }
  15:  
  16: public class AssemblyElementCollection : ConfigurationElementCollection
  17: {
  18:     protected override ConfigurationElement CreateNewElement()
  19:     {
  20:         return new AssemblyElement();
  21:     }
  22:     protected override object GetElementKey(ConfigurationElement element)
  23:     {
  24:         AssemblyElement serviceTypeElement = (AssemblyElement)element;
  25:         return serviceTypeElement.AssemblyName;
  26:     }
  27: }
  28:     
  29: public class AssemblyElement : ConfigurationElement
  30: {
  31:     [ConfigurationProperty("assemblyName", IsRequired = true)]
  32:     public string AssemblyName
  33:     {
  34:         get { return (string)this["assemblyName"]; }
  35:         set { this["assemblyName"] = value; }
  36:     }
  37: }

由于我们自定义的AssembliesResolver是对现有DefaultAssembliesResolver的扩展(尽管其程序集提供机制仅仅通过一句代码来实现),我们将类型命名为ExtendedDefaultAssembliesResolver。如下面的代码片断所示,ExtendedDefaultAssembliesResolver继承自DefaultAssembliesResolver,在重写的GetAssemblies方法中我们先通过分析上述的配置并主动加载尚未加载的程序集,然后调用基类的同名方法来提供最终的程序集。

   1: public class ExtendedDefaultAssembliesResolver : DefaultAssembliesResolver
   2: {
   3:     public override ICollection<Assembly> GetAssemblies()
   4:     {
   5:         PreLoadedAssembliesSettings settings = PreLoadedAssembliesSettings.GetSection();
   6:         if (null != settings)
   7:         {
   8:             foreach (AssemblyElement element in settings.AssemblyNames)
   9:             {
  10:                 AssemblyName assemblyName = AssemblyName.GetAssemblyName(element.AssemblyName);
  11:                 if(!AppDomain.CurrentDomain.GetAssemblies().Any(assembly=>AssemblyName.ReferenceMatchesDefinition(assembly.GetName(),assemblyName)))
  12:                 {
  13:                     AppDomain.CurrentDomain.Load(assemblyName);
  14:                 }
  15:             }
  16:         }
  17:         return base.GetAssemblies();
  18:     }
  19: }

我们在作为宿主的Hosting程序中利用如下的代码将一个ExtendedDefaultAssembliesResolver对象注册到当前HttpConfiguration的ServicesContainer上。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Uri baseAddress = new Uri("http://127.0.0.1:3721");
   6:         using (HttpSelfHostServer httpServer = new HttpSelfHostServer(new HttpSelfHostConfiguration(baseAddress)))
   7:         {
   8:             httpServer.Configuration.Services.Replace(typeof(IAssembliesResolver),new ExtendedDefaultAssembliesResolver());
   9:             //其他操作
  10:         }
  11:     }
  12: }

重新启动宿主程序后再次在浏览器输入对应的地址来访问分别定义在FooController、BarController和BazController中的Action方法Get,我们会得到如下图所示的输出结果,这正是目标Action方法执行的结果。

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
开发框架 数据可视化 .NET
.NET 中管理 Web API 文档的两种方式
.NET 中管理 Web API 文档的两种方式
312 14
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
655 4
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
968 13
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
401 9
|
缓存 安全 应用服务中间件
Web安全-HTTP Host头攻击
Web安全-HTTP Host头攻击
1663 7
|
Ubuntu 持续交付 API
如何使用 dotnet pack 打包 .NET 跨平台程序集?
`dotnet pack` 是 .NET Core 的 NuGet 包打包工具,用于将代码打包成 NuGet 包。通过命令 `dotnet pack` 可生成 `.nupkg` 文件。使用 `--include-symbols` 和 `--include-source` 选项可分别创建包含调试符号和源文件的包。默认情况下,`dotnet pack` 会先构建项目,可通过 `--no-build` 跳过构建。此外,还可以使用 `--output` 指定输出目录、`-c` 设置配置等。示例展示了创建类库项目并打包的过程。更多详情及命令选项,请参考官方文档。
1020 12
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
285 3
|
缓存 安全 应用服务中间件
Web安全-HTTP Host头攻击
Web安全-HTTP Host头攻击
1229 3
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
447 2
|
自然语言处理 C# 图形学
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试
402 0
下一篇
开通oss服务