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,如需转载请自行联系原作者



相关文章
|
1月前
|
API 网络安全 数据安全/隐私保护
.NET邮箱API发送邮件的方法有哪些
本文介绍了.NET开发中使用邮箱API发送邮件的方法,包括SmtpClient类发送邮件、MailMessage类创建邮件消息、设置SmtpClient属性、同步/异步发送、错误处理、发送HTML格式邮件、带附件邮件以及多人邮件。AokSend提供高触达发信服务,适用于大规模验证码发送场景。了解这些技巧有助于开发者实现高效、可靠的邮件功能。
|
3月前
|
存储 开发框架 NoSQL
ASP.NET WEB——项目中Cookie与Session的用法
ASP.NET WEB——项目中Cookie与Session的用法
41 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET WEB——项目创建与文件上传操作
ASP.NET WEB——项目创建与文件上传操作
48 0
|
16天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
21小时前
|
设计模式 存储 前端开发
MVC(模型-视图-控制器)是一种在Web应用程序开发中广泛使用的软件设计模式
【5月更文挑战第12天】MVC模式是Web应用开发中的常见设计模式,将逻辑、数据和界面分离,提升代码可维护性和重用性。模型处理数据逻辑,视图展示数据,控制器协调用户输入与模型视图交互。优点包括代码分离、易维护、可扩展和组件重用,促进高效灵活的开发。
6 2
|
3天前
|
XML 开发框架 .NET
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
|
6天前
|
开发框架 JSON .NET
.Net4.0 Web.config 配置实践
.Net4.0 Web.config 配置实践
|
14天前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
28 1
|
2月前
mvc.net分页查询案例——控制器(HomeController.cs)
mvc.net分页查询案例——控制器(HomeController.cs)
8 0
|
2月前
|
存储 设计模式 前端开发
请解释 Web 应用程序的 MVC(模型-视图-控制器)架构。
【2月更文挑战第26天】【2月更文挑战第89篇】请解释 Web 应用程序的 MVC(模型-视图-控制器)架构。