我們可以通過一個簡單的實例來證實這個問題。我們在一個解決方案中定義了如右圖所示的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方法執行的結果。