[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方法执行的结果。
- Enterprise Library 4.1学习笔记8----缓存应用程序块之FileDependency
- CSS实现按钮的两张图片的同步出现
- 完整部署CentOS7.2+OpenStack+kvm 云平台环境(6)--在线调整虚拟机的大小
- 关于vb中的容器
- 关于vb中的容器
- Mysql数据库之Binlog日志使用总结
- 揭秘新人机大战柯洁对手天壤 AI排名已力压Deepzen
- 一路走到java工程师,java都快出java9了,到底该如何学java?
- 网站发布合并bll问题的解决
- 痛并快乐着:浅谈大数据时代的分布式存储架构
- linux运维中的命令梳理(四)
- linux运维中的命令梳理(三)
- 轻松水印-批量提取exif信息加水印的工具
- Enterprise Library 4.1学习笔记7----缓存应用程序块之SqlDependency
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 07 . ELK Stack7.2一键多机部署脚本
- Kibana配置nginx反代并本地ca加密nginx
- Nginx WebUI管理
- Go之Gorm和BeegoORM简介及配置使用
- msyscuione:基于msys的一体化CUI开发生产环境,支持qt,llvm,ros集成常见web appstack
- monosys as 1ddlang语言选型+1ddcodebase实践选型绿色monodevelope集成常见多语言
- hostguest nativelangsys及uniform cui cross compile system
- 写好 JS 条件语句的 5 条守则
- Python元组中元素怎么删除和修改?
- Jupyter 插件太好用了
- 华为提出十大数学挑战!解出一个就是年薪百万!
- 一道 Google 的面试题
- 生产实践 | 基于 Flink 的短视频生产消费监控
- 图数据库调研
- Swift 类构造器的使用