[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名称并不能得到匹配的类型。

102249182623800.png

导致上述这个问题的原因我们在上面已经分析过了:默认注册的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方法执行的结果。

102249196531043.png
作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
22天前
|
API 网络安全 数据安全/隐私保护
.NET邮箱API发送邮件的方法有哪些
本文介绍了.NET开发中使用邮箱API发送邮件的方法,包括SmtpClient类发送邮件、MailMessage类创建邮件消息、设置SmtpClient属性、同步/异步发送、错误处理、发送HTML格式邮件、带附件邮件以及多人邮件。AokSend提供高触达发信服务,适用于大规模验证码发送场景。了解这些技巧有助于开发者实现高效、可靠的邮件功能。
|
4天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
3天前
|
监控 测试技术 API
Python Web应用程序构建
【4月更文挑战第11天】Python Web开发涉及多种框架,如Django、Flask和FastAPI,选择合适框架是成功的关键。示例展示了使用Flask创建简单Web应用,以及如何使用ORM(如SQLAlchemy)管理数据库。
17 4
|
13天前
|
SQL 安全 Go
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
在Python Web开发中,确保应用安全至关重要,主要防范SQL注入、XSS和CSRF攻击。措施包括:使用参数化查询或ORM防止SQL注入;过滤与转义用户输入抵御XSS;添加CSRF令牌抵挡CSRF;启用HTTPS保障数据传输安全;实现强身份验证和授权系统;智能处理错误信息;定期更新及审计以修复漏洞;严格输入验证;并培训开发者提升安全意识。持续关注和改进是保证安全的关键。
20 0
|
2月前
|
前端开发 JavaScript 数据管理
描述一个使用Python开发Web应用程序的实际项目经验,包括所使用的框架和技术栈。
使用Flask开发Web应用,结合SQLite、Flask-SQLAlchemy进行数据管理,HTML/CSS/JS(Bootstrap和jQuery)构建前端。通过Flask路由处理用户请求,模块化代码提高可维护性。unittest进行测试,开发阶段用内置服务器,生产环境可选WSGI服务器或容器化部署。实现了用户注册登录和数据管理功能,展示Python Web开发的灵活性和效率。
15 4
|
2月前
|
SQL 运维 监控
什么是Web应用程序防火墙,WAF与其他网络安全工具差异在哪?
总之,WAF是一种专门用于保护Web应用程序的网络安全工具,与其他网络安全工具在焦点、层级、检测方式、部署位置和针对性等方面存在差异,以确保Web应用程序的安全运行。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
123 0
|
2月前
|
SQL 安全 测试技术
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
15 0
|
2月前
|
存储 安全 数据安全/隐私保护
什么是 Web 应用程序的会话管理?如何在 Python 中实现?
什么是 Web 应用程序的会话管理?如何在 Python 中实现?
12 2
|
27天前
|
监控 JavaScript 前端开发
《理解 WebSocket:Java Web 开发的实时通信技术》
【4月更文挑战第4天】WebSocket是Java Web实时通信的关键技术,提供双向持久连接,实现低延迟、高效率的实时交互。适用于聊天应用、在线游戏、数据监控和即时通知。开发涉及服务器端实现、客户端连接及数据协议定义,注意安全、错误处理、性能和兼容性。随着实时应用需求增加,WebSocket在Java Web开发中的地位将更加重要。
|
2月前
|
Web App开发 前端开发 开发工具
介绍Web开发的基础知识
介绍Web开发的基础知识
30 7