ASP.NET Web API 控制器创建过程(一)

ASP.NET Web API 控制器创建过程(一)

前言

在前面对管道、路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内容会为大家讲解第一个部分,也是ASP.NET Web API框架跟ASP.NET MVC框架实现上存在不同的一部分。

ASP.NET Web API 控制器创建、激活过程

  • ASP.NET Web API 控制器创建过程(一)
  • ASP.NET Web API 控制器创建过程(二)
  • 未完待续

环境描述、问题的发现

在项目运用中,我们大多数会把控制器部分从主程序抽离出来放置单独的项目中,这种情况下在使用ASP.NET MVC框架的项目环境中是不会有什么问题的,因为MVC框架在创建控制器的时候会加载当前主程序引用的所有程序集并且按照执行的搜索规则(公共类型、实现IController的)搜索出控制器类型并且缓存到xml文件中。而这种方式如果在使用了默认的ASP.NET Web API框架环境下就会有一点点的问题,这里就涉及到了Web API框架的控制器创建过程中的知识。来看一下简单的示例。

(示例还是《ASP.NET Web API 开篇介绍示例》中的示例,不过做了略微的修改,符合上述的情况。)

我们还是在SelfHost环境下做示例,来看SelfHost环境下服务端配置:

示例代码1-1

    class Program
    {
        static void Main(string[] args)
        {
            HttpSelfHostConfiguration selfHostConfiguration =
                new HttpSelfHostConfiguration("http://localhost/selfhost");
            using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration))
            {
                selfHostServer.Configuration.Routes.MapHttpRoute(
                    "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });

                selfHostServer.OpenAsync();

                Console.WriteLine("服务器端服务监听已开启");
                Console.Read();
            }
        }
    }

代码1-1就是引用《ASP.NET Web API 开篇介绍示例》中的示例,在示例SelfHost项目中定义了API控制器,在这里我们需要把它注释掉,并且创建新的类库项目命名为WebAPIController,并且引用System.Web.Http.dll程序集和Common程序集,然后再定义个API控制器,也就是把原先在SelfHost项目中的控制器移动到新建的类库项目中。

示例代码1-2

using System.Web.Http;
using Common;

namespace WebAPIController
{
    public class ProductController : ApiController
    {
        private static List<Product> products;

        static ProductController()
        {
            products = new List<Product>();
            products.AddRange(
                new Product[]
                {
                    new Product(){ ProductID="001", ProductName="牙刷",ProductCategory="洗漱用品"},
                    new Product(){ ProductID="002", ProductName="《.NET框架设计—大型企业级应用框架设计艺术》", ProductCategory="书籍"}
                });
        }
        public IEnumerable<Product> Get(string id = null)
        {
            return from product in products where product.ProductID == id || string.IsNullOrEmpty(id) select product;
        }
        public void Delete(string id)
        {
            products.Remove(products.First(product => product.ProductID == id));
        }
        public void Post(Product product)
        {
            products.Add(product);
        }
        public void Put(Product product)
        {
            Delete(product.ProductID);
            Post(product);
        }
    }
}

这个时候还要记得把SelfHost项目添加WebAPIController项目的引用,要保持跟MVC项目的环境一样,然后我们在运行SelfHost项目,等待监听开启过后再使用浏览器请求服务会发现如下图所示的结果。

图1

看到图1中的显示问题了吧,未找到匹配的控制器类型。如果是MVC项目则不会有这样的问题,那么问题出在哪呢?实现方式的差异,下面就为大家来解释一下。

解决问题

在上一篇中我们最后的示意图里可以清晰的看到ASP.NET Web API框架中的管道模型最后是通过HttpControllerDispatcher类型的对象来“生成”的APIController。我们现在就来看一下HttpControllerDispatcher类型的定义。

示例代码1-3

    public class HttpControllerDispatcher : HttpMessageHandler
    {
        // Fields
        private readonly HttpConfiguration _configuration;
        private IHttpControllerSelector _controllerSelector;

        // Methods
        public HttpControllerDispatcher(HttpConfiguration configuration);
        private static HttpResponseMessage HandleException(HttpRequestMessage request, Exception exception);
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
        private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken);

        // Properties
        public HttpConfiguration Configuration { get; }
        private IHttpControllerSelector ControllerSelector { get; }
}

从示例代码1-3中可以看到HttpControllerDispatcher类型继承自HttpMessageHandler类型,由此可见,通过前面《ASP.NET Web API 管道模型》篇幅的知识我们了解到在ASP.NET Web API管道的最后一个消息处理程序实际是HttpControllerDispatcher类型,在Web API框架调用HttpControllerDispatcher类型的SendAsync()方法时实际是调用了HttpControllerDispatcher类型的一个私有方法SendAsyncInternal(),而APIController就是在这个私有方法中生成的,当然不是由这个私有方法来生成它的。下面我们就来看一下SendAsyncInternal()的基础实现。

示例代码1-4

    private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        IHttpRouteData routeData = request.GetRouteData();
        HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request);
        IHttpController controller = descriptor.CreateController(request);
    }

代码1-4很清晰的说明了APIController的生成过程,这只是片面的,这里的代码并不是全部,我们现在只需关注APIController的生成过程,暂时不去关心它的执行过程。

首先由HttpControllerDispatcher类型中的一个类型为IHttpControllerSelector的属性ControllerSelector(实则是DefaultHttpControllerSelector类型)根据HttpRequestMessage参数类型来调用IHttpControllerSelector类型里的SelectController()方法,由此获取到HttpControllerDescriptor类型的变量descriptor,然后由变量descriptor调用它的CreateController()方法来创建的控制器。

说到这里看似一个简单的过程里面蕴含的知识还是有一点的,我们首先来看ControllerSelector属性

示例代码1-5

    private IHttpControllerSelector ControllerSelector
    {
        get
        {
            if (this._controllerSelector == null)
            {
                this._controllerSelector = this._configuration.Services.GetHttpControllerSelector();
            }
            return this._controllerSelector;
        }
    }

这里我们可以看到是由HttpConfiguration类型的变量_configuration中的Servieces(服务容器)来获取的IHttpControllerSelector类型的对象。那我们获取到的究竟实例是什么类型的?

 

到这里先暂停,大家先不用记住上面的一堆废话中的内容,现在我们来看一下HttpConfiguration这个类型,我们现在只看HttpConfiguration类型,忘掉上面的一切。

示例代码1-6

    public class HttpConfiguration : IDisposable
    {
        // Fields
        private IDependencyResolver _dependencyResolver;

        // Methods
        public HttpConfiguration();
        public HttpConfiguration(HttpRouteCollection routes);
        private HttpConfiguration(HttpConfiguration configuration, HttpControllerSettings settings);

        public IDependencyResolver DependencyResolver { get; set; }
        public ServicesContainer Services { get; internal set; }

    }

在这里我们看到有字段、构造函数和属性,对于_dependencyResolver字段和对应的属性我们下面会有讲到这里就不说了。这里主要就是说明一下ServicesContainer 类型的Services属性。对于ServicesContainer类型没用的朋友可能不太清楚,实际上可以把ServicesContainer类型想象成一个IoC容器,就和IDependencyResolver类型的作用是一样的,在前面的《C#编程模式之扩展命令》一文中有涉及到ServicesContainer类型的使用,感兴趣的朋友可以去看看。

回到主题,在构造函数执行时ServicesContainer类型实例实际是由它的子类DefaultServices类型实例化而来的。而在DefaultServices类型实例化的时候它的构造函数中会将一些服务和具体实现的类型添加到缓存里。

图2

在图2中我们现在只需关注第二个红框中的IHttpControllerSelector对应的具体服务类型是DefaultHttpControllerSelector类型。

现在我们再来看代码1-5中的GetHttpControllerSelector()方法,它是有ServicesExtensions扩展方法类型来实现的。

好了现在大家的思绪可以回到代码1-4现在我们知道是由DefaultHttpControllerSelector类型的SelectController()方法来生成HttpControllerDescriptor类型的。暂时不用管HttpControllerDescriptor类型,我们先来看SelectController()方法中的实现细节。

示例代码1-7

public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        HttpControllerDescriptor descriptor;

        string controllerName = this.GetControllerName(request);
        if (this._controllerInfoCache.Value.TryGetValue(controllerName, out descriptor))
        {
            return descriptor;
        }
    }

这里可以看到controllerName是由路由数据对象RouteData来获取的,然后由_controllerInfoCache变量根据控制器名称获取到HttpControllerDescriptor实例,这里HttpControllerDescriptor实例我们暂且不管,后面的篇幅会有讲到。

重点是我们看一下_controllerInfoCache变量中的值是怎么来的?是从HttpControllerTypeCache类型的Cache属性值而来。而Cache的值则是根据HttpControllerTypeCache类型的中的InitializeCache()方法得来的,我们看下实现。

实例代码1-8

    private Dictionary<string, ILookup<string, Type>> InitializeCache()
    {
        IAssembliesResolver assembliesResolver = this._configuration.Services.GetAssembliesResolver();
        return this._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(assembliesResolver).GroupBy<Type, string>(t => t.Name.Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, Type>, string, ILookup<string, Type>>(g => g.Key, g => g.ToLookup<Type, string>(t => (t.Namespace ?? string.Empty), StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);
    }

还记得文章的主题吗?回想一下,在本篇幅的内容只涉及到代码1-8的第一句代码,后面篇幅会继续往下讲解,我们就来看一下第一句代码。

第一句是获取IAssembliesResolver类型的实例assembliesResolver,而之对应的服务则是在图2中显示出来了,就是DefaultAssembliesResolver类型,DefaultAssembliesResolver类型控制着框架搜寻的程序集范围,罪魁祸首在这。

示例代码1-9

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

看到这里大家应该明白了,在SelfHost环境下的服务端启动之后就没有加载我们所想要的WebAPIController程序集,所以才会有图1所示的问题。这个我们可以来查看一下。

将1-1代码修改如示例代码1-10.

代码1-10

    class Program
    {
        static void Main(string[] args)
        {
            HttpSelfHostConfiguration selfHostConfiguration =
                new HttpSelfHostConfiguration("http://localhost/selfhost");
            using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration))
            {
                selfHostServer.Configuration.Routes.MapHttpRoute(
                    "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });

                selfHostServer.OpenAsync();
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    Console.WriteLine(assembly.FullName.Substring(0,assembly.FullName.IndexOf("Version")));
                }
                Console.WriteLine("服务器端服务监听已开启");
                Console.Read();
            }

        }
     }

这个时候我们启动SelfHost项目,就可以查看到到底有没有我们想要的程序集,如图3。

图3

可以看一下,根本没有我们所需的,那怎么样才能正常的运行起来如一开始所说的那样呢?

示例代码1-11

using System.Web.Http.Dispatcher;
using System.Reflection;

namespace SelfHost.CustomAssembliesResolver
{
    public class LoadSpecifiedAssembliesResolver : IAssembliesResolver
    {

        public ICollection<Assembly> GetAssemblies()
        {
            AppDomain.CurrentDomain.Load("WebAPIController");
            return AppDomain.CurrentDomain.GetAssemblies();
        }
    }
}

我们通过自定义AssembliesResolver来加载我们指定的程序集,并且最后要把我们实现的替换到Web API框架中,如下代码。

示例代码1-12

    class Program
    {
        static void Main(string[] args)
        {
            HttpSelfHostConfiguration selfHostConfiguration =
                new HttpSelfHostConfiguration("http://localhost/selfhost");
            using (HttpSelfHostServer selfHostServer = new HttpSelfHostServer(selfHostConfiguration))
            {
                selfHostServer.Configuration.Routes.MapHttpRoute(
                    "DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });

                selfHostServer.Configuration.Services.Replace(typeof(IAssembliesResolver),
                    new CustomAssembliesResolver.LoadSpecifiedAssembliesResolver());

                selfHostServer.OpenAsync();
                Console.WriteLine("服务器端服务监听已开启");
                Console.Read();
            }

        }
     }

这个时候我们再运行SelfHost项目,并且使用浏览器来请求,最后结果如图4.

图4

WebHost环境

在WebHost环境中则不会发生上述所描述的问题,为什么?

示例代码1-13

    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            GlobalConfiguration.Configuration.Routes.MapHttpRoute(
              "DefaultAPI", "api/{controller}/{id}", new { controller="product",id = RouteParameter.Optional });

        }
    }

代码1-13表示着WebHost环境下的路由注册,看起来比SelfHost环境要简便的多。因为“简便”封装在GlobalConfiguration类型中了,前面的文章也对GlobalConfiguration类型做过介绍,不过并没有把重点放到IAssembliesResolver服务上。现在我们来看一下实现。

示例代码1-14

private static Lazy<HttpConfiguration> _configuration = new Lazy<HttpConfiguration>(delegate {
            HttpConfiguration configuration = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));
            configuration.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
            configuration.Services.Replace(typeof(IHttpControllerTypeResolver), new WebHostHttpControllerTypeResolver());
            configuration.Services.Replace(typeof(IHostBufferPolicySelector), new WebHostBufferPolicySelector());
            return configuration;
        });

我们可以清楚的看到DefaultAssembliesResolver类型被替换成了WebHostAssembliesResolver类型。为什么WebHost不会有这样的问题就都在WebHostAssembliesResolver类型当中了。

示例代码1-15

    internal sealed class WebHostAssembliesResolver : IAssembliesResolver
    {
        // Methods
        ICollection<Assembly> IAssembliesResolver.GetAssemblies()
        {
            return BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();
        }
    }

把WebHost环境中的结构修改的和SelfHost环境中的一样,然后请求则会发现不会遇到什么问题。

图5

作者:金源

出处:http://www.cnblogs.com/jin-yuan/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面

ASP.NET Web API 控制器创建过程(一)

时间: 2024-10-11 01:32:02

ASP.NET Web API 控制器创建过程(一)的相关文章

ASP.NET Web API 控制器创建过程(二)

ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来公布的,因为身体跟不上节奏感冒发烧有心无力,这样的天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病去如抽丝.这两天状态才好了一点,让我理解了什么才是革命的本钱,希望大家也多保重身体. 好了,还是回归主题,对于上一篇的内容解说的仅仅是ASP.NET Web API控制器创建过程中的一个局部知识,在接着上篇内容解说的之前,我会先回想一下上篇的内容,而且在本篇里进行整合,让我们要看到的是一个整个的创

ASP.NET Web API 控制器执行过程(一)

ASP.NET Web API 控制器执行过程(一) 前言 前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在创建过后将会做哪些工作. ASP.NET Web API 控制器执行过程 l  ASP.NET Web API 控制器执行过程(一) l ASP.NET Web API 控制器执行过程(二) 控制器执行过程 我们知道控制器的生成过程都是在HttpControllerDispatcher类型中来操作的

ASP.NETWeb API 控制器创建过程(二)

ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病去如抽丝.这两天状态才好了一点,让我理解了什么才是革命的本钱,希望大家也多保重身体. 好了,还是回归主题,对于上一篇的内容讲解的只是ASP.NET Web API控制器创建过程中的一个局部知识,在接着上篇内容讲解的之前,我会先回顾一下上篇的内容,并且在本篇里进行整合,让我们要看到的是一个整个的创建过

ASP.NET Web API 过滤器创建、执行过程(二)

前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器中,当然默认的基础服务也是有的,并且根据这些提供程序所获得的的过滤器信息集合进行排序.本篇就会对过滤器在创建完之后所做的一系列操作进行讲解. ASP.NET Web API 过滤器创建.执行过程(二) FilterGrouping过滤器分组类型 FilterGrouping类型是ApiController类型中的私有类型,它的作用就如同它的命名一样,用来对过

ASP.NET Web API 过滤器创建、执行过程(一)

前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就如今天的这个篇幅就是在上面内容之后所看到的一个知识要点之一. ASP.NET Web API 过滤器创建.执行过程(一) 下面就来讲解一下在ASP.NET Web API框架中过滤器的创建.执行过程. 过滤器所在的位置 图1 图1所示的就是控制器执行过程很粗略的表示. 通过上一篇内容我们了解到控制器方法选择器最后返回的并不是控制器方法,而是对于控制器方法描述

[转]使用ASP.NET Web API 2创建OData v4 终结点

本文转自:http://www.cnblogs.com/farb/p/ODataAspNetWebAPI.html 开放数据协议(Open Data Protocol[简称OData])是用于Web的数据访问协议.OData提供了一种对数据集进行CRUD操作(Create,Read,Update,Delete)的统一方式.Asp.Net Web API支持该协议的v3 和v4版,甚至可以创建一个和v3终结点并排运行的v4终结点.该博文演示了如何创建支持CRUD操作的OData v4终结点. 用到

asp.net web api 控制器

1控制器操作的参数 控制器操作的参数可以是内置类型也可以是自定义类型,无参也是允许的. 2控制器操作返回值 类型 说明 void 操作返回值为void时,Web API返回空HTTP响应,其状态码为204(无内容) HttpResponseMessage Web api会将此返回值直接转换为HTTP消息 IHttpActionResult 接口形式 内置类型或自定义类型 无   2.1返回值为HttpResponseMessage 返回值为此类型时,有两种设置方式. 第一种调用HttpRespo

【Web API系列教材】1.3 — 实战:用ASP.NET Web API和Angular.js创建单页面应用程序(下)

练习2:创建SPA界面 在本练习中,你将首先创建Geek Quiz的web前端,使用AngularJS专注于单页面应用程序的交互.然后你将使用CSS3来执行丰富的动画和提供一个当从一个问题转换到另一个问题时切换上下文的可视化效果以加强用户体验. 任务1:使用AngularJS来创建SPA界面 在本任务中,你将使用AngularJS来实现Geek Quiz应用程序的客户端.AngularJS是一个开源的JavaScript框架,它能够搭配MVC以加强基于浏览器的应用程序,使其对于开发和测试都更加便

ASP.NET Web API中的依赖注入

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 什么是依赖注入 依赖,就是一个对象需要的另一个对象,比如说,这是我们通常定义的一个用来处理数据访问的存储,让我们用一个例子来解释,首先,定义一个领域模型如下: namespace Pattern.DI.MVC.Models{ public cl