[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方法執行的結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM