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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    classProgram
    {
         staticvoidMain( string [] args)
         {
             HttpSelfHostConfigurationselfHostConfiguration=
                 newHttpSelfHostConfiguration( "http://localhost/selfhost" );
             using  (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
usingSystem.Web.Http;
usingCommon;
  
namespaceWebAPIController
{
    publicclassProductController : ApiController
    {
         privatestaticList<Product>products;
  
         staticProductController()
         {
             products=newList<Product>();
             products.AddRange(
                 newProduct[] 
                 {
                     newProduct(){ ProductID= "001" , ProductName= "牙刷" ,ProductCategory= "洗漱用品" },
                     newProduct(){ ProductID= "002" , ProductName= "《.NET框架设计—大型企业级应用框架设计艺术》" , ProductCategory= "书籍" }
                 });
         }
         publicIEnumerable<Product>Get(stringid= null )
         {
             returnfromproductinproductswhereproduct.ProductID==id|| string .IsNullOrEmpty(id) selectproduct;
         }
         publicvoidDelete(stringid)
         {
             products.Remove(products.First(product=>product.ProductID==id));
         }
         publicvoidPost(Productproduct)
         {
             products.Add(product);
         }
         publicvoidPut(Productproduct)
         {
             Delete(product.ProductID);
             Post(product);
         }
    }
}


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

1

wKiom1PkKBXzjhSQAAJmM9l206Y693.jpg

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

 

解决问题

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

示例代码1-3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    publicclassHttpControllerDispatcher : HttpMessageHandler
    {
         //Fields
         privatereadonlyHttpConfiguration_configuration;
         privateIHttpControllerSelector_controllerSelector;
  
         //Methods
         publicHttpControllerDispatcher(HttpConfigurationconfiguration);
         privatestaticHttpResponseMessageHandleException(HttpRequestMessagerequest, Exceptionexception);
         protectedoverrideTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest, CancellationTokencancellationToken);
         privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken);
  
         //Properties
         publicHttpConfigurationConfiguration {  get ; }
         privateIHttpControllerSelectorControllerSelector {  get ; }
     }


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

示例代码1-4

1
2
3
4
5
6
    privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken)
    {
         IHttpRouteDatarouteData=request.GetRouteData();
         HttpControllerDescriptordescriptor= this .ControllerSelector.SelectController(request);
         IHttpControllercontroller=descriptor.CreateController(request);
    }


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

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

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

示例代码1-5

1
2
3
4
5
6
7
8
9
10
11
    privateIHttpControllerSelectorControllerSelector
    {
         get
         {
             if  ( this ._controllerSelector== null )
             {
                 this ._controllerSelector= this ._configuration.Services.GetHttpControllerSelector();
             }
             returnthis._controllerSelector;
         }
     }


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

 

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

示例代码1-6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    publicclassHttpConfiguration : IDisposable
    {
         //Fields
         privateIDependencyResolver_dependencyResolver;
  
         //Methods
         publicHttpConfiguration();
         publicHttpConfiguration(HttpRouteCollectionroutes);
         privateHttpConfiguration(HttpConfigurationconfiguration, HttpControllerSettingssettings);
  
         publicIDependencyResolverDependencyResolver {  get set ; }
         publicServicesContainerServices {  get ; internalset; }
  
    }


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

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

2

wKiom1PkKIexnvmYAAJWu2SzIhU247.jpg

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

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

 

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

示例代码1-7

1
2
3
4
5
6
7
8
9
10
publicvirtualHttpControllerDescriptorSelectController(HttpRequestMessagerequest)
    {
         HttpControllerDescriptordescriptor;
  
         stringcontrollerName= this .GetControllerName(request);
         if  ( this ._controllerInfoCache.Value.TryGetValue(controllerName, outdescriptor))
         {
             returndescriptor;
         }
    }


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

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

实例代码1-8

 

1
2
3
4
5
    privateDictionary< string , ILookup< string , Type>>InitializeCache()
    {
         IAssembliesResolverassembliesResolver= this ._configuration.Services.GetAssembliesResolver();
        returnthis._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

1
2
3
4
5
6
7
8
    publicclassDefaultAssembliesResolver : IAssembliesResolver
    {
         //Methods
         publicvirtualICollection<Assembly>GetAssemblies()
         {
             returnAppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
         }
    }


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

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

代码1-10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    classProgram
    {
         staticvoidMain( string [] args)
         {
             HttpSelfHostConfigurationselfHostConfiguration=
                 newHttpSelfHostConfiguration( "http://localhost/selfhost" );
             using  (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(selfHostConfiguration))
             {
                 selfHostServer.Configuration.Routes.MapHttpRoute(
                     "DefaultApi" "api/{controller}/{id}" new  { id=RouteParameter.Optional });
  
                 selfHostServer.OpenAsync();
                 foreach  (AssemblyassemblyinAppDomain.CurrentDomain.GetAssemblies())
                 {
                     Console.WriteLine(assembly.FullName.Substring(0,assembly.FullName.IndexOf( "Version" )));
                 }
                 Console.WriteLine( "服务器端服务监听已开启" );
                 Console.Read();
             }
             
         }
}


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

3

wKiom1PkKMHwSnOHAAJBou1umsg045.jpg

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

示例代码1-11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
usingSystem.Web.Http.Dispatcher;
usingSystem.Reflection;
  
namespaceSelfHost.CustomAssembliesResolver
{
    publicclassLoadSpecifiedAssembliesResolver : IAssembliesResolver
    {
  
         publicICollection<Assembly>GetAssemblies()
         {
             AppDomain.CurrentDomain.Load( "WebAPIController" );
             returnAppDomain.CurrentDomain.GetAssemblies();
         }
    }
}


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

示例代码1-12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    classProgram
    {
         staticvoidMain( string [] args)
         {
             HttpSelfHostConfigurationselfHostConfiguration=
                 newHttpSelfHostConfiguration( "http://localhost/selfhost" );
             using  (HttpSelfHostServerselfHostServer=newHttpSelfHostServer(selfHostConfiguration))
             {
                 selfHostServer.Configuration.Routes.MapHttpRoute(
                     "DefaultApi" "api/{controller}/{id}" new  { id=RouteParameter.Optional });
  
                 selfHostServer.Configuration.Services.Replace( typeof (IAssembliesResolver),
                     newCustomAssembliesResolver.LoadSpecifiedAssembliesResolver());
  
                 selfHostServer.OpenAsync();
                 Console.WriteLine( "服务器端服务监听已开启" );
                 Console.Read();
             }
             
         }
}


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

4

wKioL1PkKfOgH3DqAAJrG3hjVgQ479.jpg

WebHost环境

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

示例代码1-13

1
2
3
4
5
6
7
8
9
    publicclassGlobal : System.Web.HttpApplication
    {
         protectedvoidApplication_Start(objectsender, EventArgse)
         {
             GlobalConfiguration.Configuration.Routes.MapHttpRoute(
               "DefaultAPI" "api/{controller}/{id}" new  { controller= "product" ,id=RouteParameter.Optional });
             
         }
}


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

示例代码1-14

1
2
3
4
5
6
7
privatestaticLazy<HttpConfiguration>_configuration=newLazy<HttpConfiguration>( delegate  {
             HttpConfigurationconfiguration=newHttpConfiguration(newHostedHttpRouteCollection(RouteTable.Routes));
             configuration.Services.Replace( typeof (IAssembliesResolver), newWebHostAssembliesResolver());
             configuration.Services.Replace( typeof (IHttpControllerTypeResolver), newWebHostHttpControllerTypeResolver());
             configuration.Services.Replace( typeof (IHostBufferPolicySelector), newWebHostBufferPolicySelector());
             returnconfiguration;
         });


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

示例代码1-15

1
2
3
4
5
6
7
8
    internalsealedclassWebHostAssembliesResolver : IAssembliesResolver
    {
         //Methods
         ICollection<Assembly>IAssembliesResolver.GetAssemblies()
         {
             returnBuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();
         }
}


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

5

wKioL1PkKhzBy1ngAAJJEp_o2Bs199.jpg

 

下一篇将会讲解一下APIController的生成过程,也就是代码1-8的第二句代码部分。




     本文转自jinyuan0829 51CTO博客,原文链接:http://blog.51cto.com/jinyuan/1537264,如需转载请自行联系原作者



相关文章
|
30天前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
81 9
|
30天前
|
存储 开发框架 .NET
.NET 8 实现无实体库表 API 部署服务
【10月更文挑战第12天】在.NET 8中,可通过以下步骤实现无实体库表的API部署:首先安装.NET 8 SDK及开发工具,并选用轻量级Web API框架如ASP.NET Core;接着创建新项目并设计API,利用内存数据结构模拟数据存储;最后配置项目设置并进行测试与部署。此方法适用于小型项目或临时解决方案,但对于大规模应用仍需考虑持久化存储以确保数据可靠性与可扩展性。
|
2月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
87 3
|
2月前
|
开发框架 前端开发 .NET
VB.NET中如何利用ASP.NET进行Web开发
在VB.NET中利用ASP.NET进行Web开发是一个常见的做法,特别是在需要构建动态、交互式Web应用程序时。ASP.NET是一个由微软开发的开源Web应用程序框架,它允许开发者使用多种编程语言(包括VB.NET)来创建Web应用程序。
54 5
|
1月前
|
监控 安全 API
Docker + .NET API:简化部署和扩展
Docker + .NET API:简化部署和扩展
35 0
|
1月前
|
监控 安全 API
最完美的扩展Docker + .NET API:简化部署和扩展
最完美的扩展Docker + .NET API:简化部署和扩展
55 0
|
1月前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
21 0
|
3月前
|
开发框架 监控 .NET
开发者的革新利器:ASP.NET Core实战指南,构建未来Web应用的高效之道
【8月更文挑战第28天】本文探讨了如何利用ASP.NET Core构建高效、可扩展的Web应用。ASP.NET Core是一个开源、跨平台的框架,具有依赖注入、配置管理等特性。文章详细介绍了项目结构规划、依赖注入配置、中间件使用及性能优化方法,并讨论了安全性、可扩展性以及容器化的重要性。通过这些技术要点,开发者能够快速构建出符合现代Web应用需求的应用程序。
51 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
46 0