下面就一些web api的一些基础内容进行阐述,然后就web api宿主承载中的实际业务问题进行解决
HttpController
HttpController的激活是由处于消息处理管道尾端的HttpRoutingDispatcher来完成的,具体来说是HttpRoutingDispatcher利用HttpControllerDispatcher实现了针对目标HttpController的激活和执行。激活目标HttpController的前提是能够正确解析出HttpController的真实类型,而类型解析需要针对加载的程序集,所以我们需要先来了解一个用于解析程序集的对象AssembliesResolver。在ASP.NET Web API的HttpController激活系统中,AssembliesResolver为目标HttpController的类型解析提供候选的程序集。换句话说,候选HttpController类型的选择范围仅限于定义在通过AssembliesResolver提供的程序集中的所有实现了IHttpController接口的类型
AssembliesResolver
所有的AssembliesResolver均实现了IAssembliesResolver接口。根据程序反射得到如下代码片段,根据代码片段得知,IAssembliesResolver提供的是程序集列表
public interface IAssembliesResolver{ ICollectionGetAssemblies();}
DefaultAssembliesResolver
默认的AssembliesResolver为DefaultAssembliesResolver,根据以下代码片段得知,默认返回的是当前应用程序域的程序集
public class DefaultAssembliesResolver : IAssembliesResolver{ public virtual ICollectionGetAssemblies() { return AppDomain.CurrentDomain.GetAssemblies().ToList (); }}
ServicesContainer
默认的AssembliesResolver就是通过ServicesContainer类型确定
Web Api的请求相当于一个管道,类似于流水线作业,每个环节都会注册自己的实现类组件来完成自己的工作。这些组件都会实现自己特定的接口,当然在预置的组件无法满足我们的业务需求时,可以自己来实现组件代码,并进行注册,ServicesContainer其实可以简单的理解为这些组件的一个IOC容器
上面所介绍的就代表着,如果采用selfHost承载web api,而且web api与宿主程序不在同一程序集中的情况,如果在用上篇的代码, 将会发现报以下错误 No type was found that matches the controller named 'User'.
上面所说的情况,分两种,第一种,单个api程序集,多个api程序集
1.如果api程序集全部写在一个单独的程序集中的情况
1.1 直接通过Assembly加载外部程序集
static void Main(string[] args) { // 如果 API 处于外部程序集,可通过以下代码加载 CM.API为其他程序集 //Assembly.Load("CM.API, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); //加载外部程序集 string baseAddress = "http://localhost:9000/"; // 启动 OWIN host WebApp.Start(url: baseAddress); Console.WriteLine("程序已启动,按任意键退出"); Console.ReadLine(); }
宿主项目还是需要引用CM.API程序集
1.2通过自定义AssembliesResolber来进行注册
public class UserResolver: DefaultAssembliesResolver { public override ICollectionGetAssemblies() { ICollection baseAssemblies = base.GetAssemblies(); List assemblies = new List (baseAssemblies); var assembly = AppDomain.CurrentDomain.BaseDirectory; string path = assembly + "\\" + "CM.API.dll"; var controllersAssembly = Assembly.LoadFrom(path); baseAssemblies.Add(controllersAssembly); return assemblies; } }
然后宿主启动时进行注册
static void Main(string[] args) { var config = new HttpSelfHostConfiguration("http://localhost:8083"); config.Routes.MapHttpRoute( "API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); //注册UserResolver,加载程序集 config.Services.Replace(typeof(IAssembliesResolver), new UserResolver()); using (var server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); } }
2.当然在业务开发中如果API被分散开发到多个程序集中,这种情况当然根据项目框架区分,各有利弊
2.1 通过上述的UserServoler进行加载
public class UserResolver: DefaultAssembliesResolver { public override ICollectionGetAssemblies() { ICollection baseAssemblies = base.GetAssemblies(); List assemblies = new List (baseAssemblies); var assembly = AppDomain.CurrentDomain.BaseDirectory; string path = assembly + "\\" + "CM.API.dll"; var controllersAssembly = Assembly.LoadFrom(path); baseAssemblies.Add(controllersAssembly); //加载多个程序集 不建议写死 string path2 = assembly + "\\" + "CM.API2.dll"; var controllersAssembly2 = Assembly.LoadFrom(path2); baseAssemblies.Add(controllersAssembly2); return assemblies; } }
上述代码只是展示加载的一种方式,只是得到程序集的方式需要去进行配置编码,不建议直接写死在代码里
2.2 通过自定配置类,进行配置文件读取加载
添加自定义配置类
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Configuration;using System.Reflection; namespace CM.SelfHost{ public class AssembliesLoad : ConfigurationSection { [ConfigurationProperty("", IsDefaultCollection = true)] public AssemblyElementCollection AssemblyNames { get { return (AssemblyElementCollection)this[""]; } } public static AssembliesLoad GetSection() { return ConfigurationManager.GetSection("AssembliesLoad") as AssembliesLoad; } } public class AssemblyElementCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() { return new AssemblyElement(); } protected override object GetElementKey(ConfigurationElement element) { AssemblyElement serviceTypeElement = (AssemblyElement)element; return serviceTypeElement.AssemblyName; } } public class AssemblyElement : ConfigurationElement { [ConfigurationProperty("assemblyName", IsRequired = true)] public string AssemblyName { get { return (string)this["assemblyName"]; } set { this["assemblyName"] = value; } } }}
添加配置文件,如下,加入ConfigSections节点以及AssembliesLoad节点
添加AssemblyResolver类,并注册
public class WebApiResolver: DefaultAssembliesResolver { public override ICollectionGetAssemblies() { AssembliesLoad settings = AssembliesLoad.GetSection(); if (null != settings) { foreach (AssemblyElement element in settings.AssemblyNames) { AssemblyName assemblyName = AssemblyName.GetAssemblyName(element.AssemblyName); if (!AppDomain.CurrentDomain.GetAssemblies().Any(assembly => AssemblyName.ReferenceMatchesDefinition(assembly.GetName(), assemblyName))) { AppDomain.CurrentDomain.Load(assemblyName); } } } return base.GetAssemblies(); } }
注册Resolver
static void Main(string[] args) { //Assembly.Load("CM.API, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); //加载外部程序集 var config = new HttpSelfHostConfiguration("http://localhost:8083"); config.Routes.MapHttpRoute( "API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); config.Services.Replace(typeof(IAssembliesResolver), new UserResolver()); using (var server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); } }
运行会报错 其他信息: 配置系统未能初始化
查阅一些说明之后发现,如果存在自定义配置ConfigSections节点,那么必须在第一个节点,修改app.confg如下
至此,web api的承载方式就已经阐述完毕,说一下个人倾向,通过两篇文章对承载的了解,我喜欢使用OwinselfHost承载,并采用自定义配置文件的方式加载外部api程序集,Owin有跨平台功能,但没有去进行验证,大家有兴趣可以去验证下