「ASP.NET Web API」自定义的宿主环境中使用外部程独立 Controller

2015-12-04 白若水 更多博文 » 博客 » GitHub »

原文链接 http://inskyline.com/net/2015/12/04/a-net-asp.net_WebApiuser_defined_selfhostServer.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


背景

最近打算对一个 Web API 项目做代码混淆,但由于宿主环境是 IIS,导致完全混淆后, IIS 不能很好的解析。于是决定自己写一个宿主环境。

用一个控制台项目,用一段简单的代码

 static void Main(string[] args)
        {
            var config = new HttpSelfHostConfiguration("http://127.0.0.1:3333");
            config.Routes.MapHttpRoute("default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
            var server = new HttpSelfHostServer(config);
            server.OpenAsync().Wait();

            Console.WriteLine("Server is opened");
            Console.Read();
        }

将外部的 Controller dll 以及相关的 dll 引入到当前的程序下。打开浏览器请求。

出现以下错误

<Error>
<Message>
未找到与请求 URI“http://127.0.0.1:3333/api/Product/Values”匹配的 HTTP 资源。
</Message>
<MessageDetail>未找到与名为“Product”的控制器匹配的类型。</MessageDetail>
</Error>

经分析导致错误的原因是:默认注册的 DefaultAssembliesResolver 仅仅提供当前应用程序域加载的程序集。我引入的程序集并没有加载在当前程序域中。

于是先继承 DefaultAssembliesResolver 将独立的 dll 添加到当前程序域中。

代码如下:

public class CustomAssembliesResolver : DefaultAssembliesResolver
    {
        public override ICollection<Assembly> GetAssemblies()
        {
            ICollection<Assembly> baseAssemblies = base.GetAssemblies();

            List<Assembly> assemblies = new List<Assembly>(baseAssemblies);

            var controllersAssembly = Assembly.LoadFrom(@"TestHostApi.dll");

            baseAssemblies.Add(controllersAssembly);

            return baseAssemblies;
        }
    }

对宿主程序做一小点修改为:

            var config = new HttpSelfHostConfiguration("http://127.0.0.1:3333");
            config.Routes.MapHttpRoute("default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
            var server = new HttpSelfHostServer(config);

            CustomAssembliesResolver assemblyResolver = new CustomAssembliesResolver();
            server.Configuration.Services.Replace(typeof(IAssembliesResolver), new ExtendedDefaultAssembliesResolver());

            server.OpenAsync().Wait();

            Console.WriteLine("Server is opened");
            Console.Read();

这样再次打开浏览器访问,得到了我想要的结果。

追溯

问题解决了,但 HttpSelfHostServer 到底是如何工作的,不妨追根寻源,探究以它本后的逻辑。

继承体系


├─System.Object 
│  ├─HttpMessageHandler
│     └─DelegatingHandler
│       └─System.Web.Http.HttpServer
            └─System.Web.Http.SelfHost.HttpSelfHostServer

HttpMessageHandler

ASP.NET Web 核心框架是基于消息的管道。所有的消息都经过 HttpMessageHandler 处理,这个独立的抽象管道独立于寄宿环境。

代码:

 public abstract class HttpMessageHandler : IDisposable
    {
       public void Dispose();
       protected virtual void Dispose(bool disposing);
       protected abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
   }

DelegatingHandler

ASP.NET Web API 消息处理管道是通过一组有序的 HttpMessagHandler 『首尾相连』而成,具体实现是通过 DelegatingHandler 这个类型来完成的。

代码:

public abstract class DelegatingHandler : HttpMessageHandler
    {  
        protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
        public HttpMessageHandler InnerHandler get;  set; }
    }

DelegatingHandler 根据其 InnerHandler 获得被委托的 HttpMessageHandler 对象的引用,对消息处理。

HttpServer

HttpServer 直接继承自 DelegatingHandler。它有两个重要的只读属性(ConfigurationDispatcher),前者得到用于配置整个消息处理管道的 HttpConfiguration 对象,另外一个属性Dispatcher 返回的是处于整个消息处理管道『尾端』的HttpMessageHandler

代码:

    public class HttpServer : DelegatingHandler
    {
          public HttpConfiguration     Configuration { get; }
          public HttpMessageHandler    Dispatcher { get; }

          public HttpServer();
          public HttpServer(HttpMessageHandler dispatcher);
          public HttpServer(HttpConfiguration configuration);
          public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher);

          protected override void Dispose(bool disposing);   
          protected virtual void Initialize();  
          protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
    }

HttpServerConfigurationDispatcher 属性均可以在相应的构造函数中初始化。如果在构造HttpServer 的时候没有显式指定这两个属性的值(调用默认的无参构造函数创建 HttpServer),在默认情况下会创建一个HttpConfiguration作为Configuration的属性值,而作为Dispatcher属性值的则是一个HttpRoutingDispatcher对象,该类型定义在命名空间System.Web.Http.Dispatcher下。

整个管道都是由HttpConfiguration 进行配置:

HttpConfiguration

代码


public class HttpConfiguration : IDisposable
    {
        public HttpConfiguration();
        public HttpConfiguration(HttpRouteCollection routes);
        // 获取或设置与此实例关联的依赖关系解析程序。
        //返回结果:依赖关系解析程序。
        public IDependencyResolver DependencyResolver { get; set; }
        //筛选器列表。
        public HttpFilterCollection Filters { get; }
        public MediaTypeFormatterCollection Formatters { get; }
        public IncludeErrorDetailPolicy IncludeErrorDetailPolicy { get; set; }
        public Action<HttpConfiguration> Initializer { get; set; }
        public Collection<DelegatingHandler> MessageHandlers { get; }
        public ParameterBindingRulesCollection ParameterBindingRules { get; internal set; }
        public ConcurrentDictionary<object, object> Properties { get; }
        public HttpRouteCollection Routes { get; }    
        //获取与此实例关联的默认服务的容器。
        // 返回结果: 
        // 包含此实例的默认服务的 System.Web.Http.Controllers.ServicesContainer。
        public ServicesContainer Services { get; internal set; }
        public string VirtualPathRoot { get; }
        public void Dispose();
        protected virtual void Dispose(bool disposing);
    }

其中属性 DependencyResolverServices 提供了很大的灵活性。

HttpSelfHostServer

HttpSelfHostServer 是一个密封类,继承自HttpServer。它的构造中用了 Configuration的派生类(HttpSelfHostConfiguration),并提供监听的打开和关闭方法。

代码:

 //直接侦听 HTTP 的 System.Web.Http.HttpServer 的实现。
    public sealed class HttpSelfHostServer : HttpServer
    {

        public HttpSelfHostServer(HttpSelfHostConfiguration configuration);

        // configuration: 配置。
        // dispatcher:调度程序。
        public HttpSelfHostServer(HttpSelfHostConfiguration configuration, HttpMessageHandler dispatcher);

        // 一个表示异步 System.Web.Http.HttpServer 关闭操作的 System.Threading.Tasks.Task。
        public Task CloseAsync();
        protected override void Dispose(bool disposing);

        // 返回结果: 
        // 一个表示异步 System.Web.Http.HttpServer 打开操作的 System.Threading.Tasks.Task。一旦成功完成此任务,服务器将运行。
        public Task OpenAsync();
    }

HttpSelfHostConfiguration 继承自 HttpConfiguration ,有一个 ServicesContainer 类型的属性Services,此属性有大用‼️

ServicesContainer

体系结构

System.Object
└─System.Web.Http.Controllers.ServicesContainer
    └─System.Web.Http.Controllers.ControllerServices
        └─System.Web.Http.Services.DefaultServices

ServicesContainer 用于服务管理,有添加修改和删除等操作方法。

IAssembliesResolver

ASP.NET Web APIHttpController 激活系统中,AssembliesResolver 为目标 HttpController 类型解析提供候选的程序集。该接口定义在命名空间 System.Web.Http.Dispatcher 下。

定义:

  public interface IAssembliesResolver
   {
       ICollection<Assembly> GetAssemblies();
   }

ASP.NET Web API 有一个重要的类 DefaultAssembliesResolver 继承了 IAssembliesResolver

    public class DefaultAssembliesResolver : IAssembliesResolver
    {
        public virtual ICollection<Assembly> GetAssemblies()
        {
            return AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
        }
    }

DefaultAssembliesResolver 的实现上可以看到默认的注射器只能注射当前程序域的程序集。

总结

分析到这里,终于了然了,自自定义的宿主程序中要引入外部的 Controller,必须重新实现 IAssembliesResolver,或者继承 DefaultAssembliesResolver重写 GetAssemblies()方法。

ASP.NET Web 是一个严谨而具有高扩展性的框架,HttpSelfHostServer是基于这个框架实现,要实现一个完善的宿主程序,必须对这个机制有一定的了解,利用该『组织』提供的服务和协议,站在巨人肩膀上,才能发现『万有引力』😄!