ASP.NET Web API的Controller是如何被创建的?
Web API调用请求的目标是定义在某个HttpController类型中的某个Action方法,所以消息处理管道最终需要激活目标HttpController对象。调用请求的URI会携带目标HttpController的名称,该名称经过路由解析之后会作为路由变量保存到一个HttpRouteData对象中,而后者会被添加到代表当前请求的HttpRequestMessage对象的属性字典中。ASP.NET Web API据此解析出目标HttpController的类型,进而实现针对目标HttpController实例的激活。[本文已经同步到《How ASP.NET Web API Works?》]
目录
一、程序集的解析
二、HttpController类型的解析
1、DefaultHttpControllerTypeResolver
2、HttpController类型的缓存
三、HttpController的选择
1、DefaultHttpControllerSelector
2、获取目标HttpController的名称
3、建立HttpController名称与HttpControllerDescriptor之间的映射
4、根据请求选择HttpController
四、HttpController的创建
1、HttpControllerActivator
2、DefaultHttpControllerActivator
3、DependencyResolver
4、HttpRequestMessage中的DependencyResolver
5、DependencyResolver在DefaultHttpControllerActivator中的应用
一、程序集的解析
在ASP.NET Web API的HttpController激活系统中,AssembliesResolver为目标HttpController类型解析提供候选的程序集。换句话说,候选HttpController类型的选择范围仅限于定义在由AssembliesResolver提供的程序集中的所有实现了IHttpController接口的类型。所有的AssembliesResolver均实现了接口IAssembliesResolver,该接口定义在命名空间“System.Web.Http.Dispatcher”下,如果未作特别说明,本节新引入的类型均定义在此命名空间下。如下面的代码片断所示,IAssembliesResolver接口中仅仅定义了一个唯一的GetAssemblies方法,该方法返回的正是提供的程序集列表。
1: public interface IAssembliesResolver
2: {
3: ICollection<Assembly> GetAssemblies();
4: }
默认使用的AssembliesResolver类型为DefaultAssembliesResolver。如下面的代码片断所示,DefaultAssembliesResolver在实现的GetAssemblies方法中直接返回当前应用程序域加载的所有程序集列表。
1: public class DefaultAssembliesResolver : IAssembliesResolver
2: {
3: public virtual ICollection<Assembly> GetAssemblies()
4: {
5: return AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
6: }
7: }
DefaultAssembliesResolver是默认使用的AssembliesResolver,那么默认的AssembliesResolver类型在ASP.NET Web API是如何确定的呢?要回答这个问题,需要涉及到另一个重要的类型ServicesContainer,它定义在命名空间“System.Web.Http.Controllers”下。
由于DefaultAssembliesResolver在为HttpController类型解析提供的程序集仅限于当前应用程序域已经加载的程序集,如果目标HttpController定义在尚未加载的程序集中,我们不得不预先加载它们。但是这样的问题只会发生在Self Host寄宿模式下,如果采用Web Host寄宿模式则无此困扰,原因在于后者默认使用的是另一个AssembliesResolver类型。我们知道在Web Host寄宿模式下用于配置ASP.NET Web API消息处理管道的是通过类型GlobalConfiguration的静态只读属性Configuration返回的HttpConfiguration对象。从如下的代码片断我们可以发现,当GlobalConfiguration的Configuration属性被第一次访问的时候,在ServicesContainer中注册的AssembliesResolver会被替换成一个类型为WebHostAssembliesResolver的对象。
1: public static class GlobalConfiguration
2: {
3: //其他成员
4: static GlobalConfiguration()
5: {
6: _configuration = new Lazy<HttpConfiguration>(delegate
7: {
8: HttpConfiguration configuration = new HttpConfiguration( new HostedHttpRouteCollection(RouteTable.Routes));
9: configuration.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver());
10: //其他操作
11: return configuration;
12: });
13: //其他操作
14: }
15:
16: public static HttpConfiguration Configuration
17: {
18: get
19: {
20: return _configuration.Value;
21: }
22: }
23: }
WebHostAssembliesResolver是一个定义在程序集“System.Web.Http.WebHost.dll”中的内部类型。从如下的代码片断可以看出WebHostAssembliesResolver在实现的GetAssemblies方法中直接通过调用BuildManager的GetReferencedAssemblies方法来获取最终提供的程序集。
1: internal sealed class WebHostAssembliesResolver : IAssembliesResolver
2: {
3: ICollection<Assembly> IAssembliesResolver.GetAssemblies()
4: {
5: return BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToList<Assembly>();
6: }
7: }
由于BuildManager的GetReferencedAssemblies方法几乎返回了在运行过程中需要的所有程序集,如果我们将HttpController类型定义在单独的程序集中,我们只要确保该程序集已经正常部属就可以了。如果有人对此感兴趣,可以试着将上面演示的实例从Self Host寄宿模式转换成Web Host寄宿模式,看看ASP.NET Web API的HttpController激活系统能否正常解析出分别定义在Foo.dll、Bar.dll和Baz.dll中的HttpController类型。
二、HttpController类型的解析
注册在当前ServicesContainer上的AssembliesResolver对象为HttpController类型的解析提供了可供选择的程序集,真正用于解析HttpController类型的是一个名为HttpControllerTypeResolver的对象。所有的HttpControllerTypeResolver类型均实现了接口IHttpControllerTypeResolver,如下面的代码片断所示,定义其中的唯一方法GetControllerTypes借助于提供的AssembliesResolver解析出所有的HttpController类型。
1: public interface IHttpControllerTypeResolver
2: {
3: ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
4: }
与AssembliesResolver注册方式类似,默认使用的HttpControllerTypeResolver同样是注册在当前HttpConfiguration的ServicesContainer对象上。我们可以通过ServicesContainer具有如下定义的扩展方法GetHttpControllerTypeResolver得到这个注册的HttpControllerTypeResolver对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerTypeResolver GetHttpControllerTypeResolver(this ServicesContainer services);
5: }
我们同样可以通过HttpConfiguration默认采用的DefaultServices的构造函数得到默认注册的HttpControllerTypeResolver对象的类型。如下面的代码片断所示,这个默认注册的HttpControllerTypeResolver是一个类型为DefaultHttpControllerTypeResolver的对象。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerTypeResolver>(new DefaultHttpControllerTypeResolver());
8: }
9: }
1、DefaultHttpControllerTypeResolver
如下面的代码片断所示, DefaultHttpControllerTypeResolver具有一个Predicate<Type>类型的只读属性IsControllerTypePredicate,返回的委托对象用于判断指定的类型是否是一个有效的HttpController类型。
1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
2: {
3: public DefaultHttpControllerTypeResolver();
4: public DefaultHttpControllerTypeResolver(Predicate<Type> predicate);
5:
6: public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver);
7:
8: protected Predicate<Type> IsControllerTypePredicate { get; }
9: }
如果我们具有特别的HttpController类型有效性验证规则,可以在调用构造函数实例化DefaultHttpControllerTypeResolver对象时通过参数指定这个Predicate<Type>委托对象。在默认情况下,这个自动初始化的Predicate<Type>对象体现了默认采用的HttpController类型有效验证规则。具体来说,默认情况下一个给定的类型必须同时满足如下的条件才是一个有效的HttpController类型。
- 是一个外部可见(IsVisible = true)的实例(IsAbstract = false)类(IsClass = true)。
- 类型直接或者间接实现了接口IHttpController。
- 类型名称必须以“Controller”为后缀,但是不区分大小写(可以使用“controller”作为后缀)。
用于提供所有有效HttpController类型的GetControllerTypes方法的实现逻辑其实很简单。它通过指定的AssembliesResolver得到一个程序集列表,对于定义在这些程序集中的所有类型,如果满足上述的要求就是返回的HttpController类型之一。定义在类型DefaultHttpControllerTypeResolver中的针对有效HttpController类型的解析逻辑基本上体现在如下所示的代码中。
1: public class DefaultHttpControllerTypeResolver : IHttpControllerTypeResolver
2: {
3: //其他成员
4: public virtual ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
5: {
6: List<Type> types = new List<Type>();
7: foreach (Assembly assembly in assembliesResolver.GetAssemblies())
8: {
9: foreach (Type type in assembly.GetTypes())
10: {
11: if (this.IsControllerTypePredicate(type))
12: {
13: types.Add(type);
14: }
15: }
16: }
17: return types;
18: }
19: }
2、HttpController类型的缓存
由于针对所有HttpController类型的解析需要大量使用到反射,这是一个相对耗时的过程,所以ASP.NET Web API会对解析出来的HttpController类型进行缓存。具体的缓存实现在具有如下定义的HttpControllerTypeCache类型中,这是一个定义在程序集“System.Web.Http.dll”中的内部类型。
1: internal sealed class HttpControllerTypeCache
2: {
3: //其他成员
4: internal Dictionary<string, ILookup<string, Type>> Cache { get; }
5: }
缓存的HttpController类型通过只读属性Cache获取,这是一个类型为Dictionary<string, ILookup<string, Type>>的字典对象。该字典的Key表示HttpController的名称(HttpController类型名称去除“Controller”后缀),其Value返回的ILookup<string, Type>对象包含一组具有相同名称的HttpController类型列表,自身的Key表示HttpController类型的命名空间。
三、目标HttpController的选择
AssembliesResolver仅仅是将所有合法的HttpController类型解析出来,针对具体的调用请求,系统必须从中选择一个与当前请求匹配的HttpController类型出来。HttpController的选择通过HttpControllerSelector对象来完成,所有的HttpControllerSelector类型均实现了具有如下定义的接口IHttpControllerSelector。
1: public interface IHttpControllerSelector
2: {
3: IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
4: HttpControllerDescriptor SelectController(HttpRequestMessage request);
5: }
如上面的代码片断所示,该接口中定义了GetControllerMapping和SelectController两个方法。GetControllerMapping返回一个描述所有HttpController类型的HttpControllerDescriptor对象与对应的HttpController名称之间的映射关系。针对请求的HttpController选择实现在SelectController方法中,它返回描述目标HttpController的HttpControllerDescriptor对象。
1、DefaultHttpControllerSelector
默认使用HttpControllerSelector依然注册到当前的ServicesContainer对象中,我们可以调用ServicesContainer如下所示的扩展方法GetHttpControllerSelector得到注册的HttpControllerSelector对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerSelector GetHttpControllerSelector(this ServicesContainer services);
5: }
如下的代码片断所示,默认使用的DefaultServices在初始化的过程中会根据指定的HttpConfiguration对象创建一个DefaultHttpControllerSelector对象,并将其注册为默认的HttpControllerSelector。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerSelector>(new DefaultHttpControllerSelector(configuration));
8: }
9: }
如下面的代码片断所示,DefaultHttpControllerSelector不仅仅实现了IHttpControllerSelector接口中定义的两个方法,还定义了另一个名为GetControllerName方法,我们可以调用此方法根据指定HttpRequestMessage对象得到该请求访问的目标HttpController的名称。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: public DefaultHttpControllerSelector(HttpConfiguration configuration);
4:
5: public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
6: public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request);
7:
8: public virtual string GetControllerName(HttpRequestMessage request);
9: }
2、获取目标HttpController的名称
如果采用Web Host寄宿模式,消息管道的缔造者HttpControllerHandler在根据当前HTTP上下文创建用于表示请求的HttpRequestMessage对象后,会将ASP.NET路由系统解析当前请求得到的RouteData对象转换成HttpRouteData对象并添加到HttpRequestMessage的属性字典中。对于Self Host寄宿模式来说,处于消息处理管道末端的HttpRoutingDispatcher会利用ASP.NET Web API的路由系统对当前请求进行路由解析并直接得到封装了路由数据的HttpRouteData对象,此HttpRouteData同样会被添加到表示当前请求的HttpRequestMessage对象的属性字典之中。
由于被附加到当前请求的HttpRouteData已经包含了目标HttpController的名称(对应的变量名为“controller”),所以我们可以从HttpRequestMessage中直接获取目标HttpController的名称。如下面的代码片断所示,DefaultHttpControllerSelector的GetControllerName方法也是按照这样的逻辑从指定的HttpMessageMessage中提取目标HttpController的名称。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: //其他成员
4: public virtual string GetControllerName(HttpRequestMessage request)
5: {
6: IHttpRouteData routeData = request.GetRouteData();
7: if (routeData == null)
8: {
9: return null;
10: }
11: string str = null;
12: routeData.Values.TryGetValue<string>("controller", out str);
13: return str;
14: }
15: }
3、建立HttpController名称与HttpControllerDescriptor之间的映射
DefaultHttpControllerSelector 的GetControllerMapping方法会返回一个类型为IDictionary<string, HttpControllerDescriptor>的字典,它包含了描述所有HttpController的HttpControllerDescriptor对象与对应HttpController名称之间的映射关系。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: //其他成员
4: private readonly HttpControllerTypeCache _controllerTypeCache;
5: public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
6: }
GetControllerMapping方法的实现逻辑其实很简单。如上面的代码片断所示,DefaultHttpControllerSelector具有一个HttpControllerTypeCache类型的只读字段,通过它可以得到HttpController类型与名称之间的关系,GetControllerMapping方法只需要根据HttpController类型生成对应的HttpControllerDescriptor对象即可。但是有个问题必须要考虑,由于同名的HttpController类型可能定义在不同的命名空间下,而且这里所指的“HttpController名称”是不区分大小写的,所以一个HttpController名称可能对应着多个HttpController类型,这也是为何HttpControllerTypeCache缓存的数据是一个类型为Dictionary<string, ILookup<string, Type>>的字典对象的原因。
4、根据请求选择HttpController
其实HttpControllerSelector的终极目标还是根据请求实现对目标HttpController的选择,这体现在它的SelectController方法上。对于默认注册的DefaultHttpControllerSelector来说,其SelectController方法的实现逻辑非常简单,它只需要调用GetControllerName方法从给定的HttpRequestMessage提取目标HttpController的名称,然后根据此名称从GetControllerMapping方法的返回值中提取对应的HttpControllerDescriptor对象即可。实现在SelectController方法中针对请求的HttpController选择机制虽然简单,但是针对几种特殊情况的处理机制我们不应该忽视。
- 在通过AssembliesResolver提供的程序集中并不曾定义这么一个有效的HttpController类型。
- 在通过AssembliesResolver提供的程序集中定义了多个同名的HttpController类型,可能是多个HttpController类型在不区分大小写情况下同名,或者是完全同名的多个HttpController类型定义在不同的命名空间下。
四、HttpController的创建
1: public class HttpControllerDescriptor
2: {
3: //其他成员
4: public virtual IHttpController CreateController(HttpRequestMessage request);
5: }
1、HttpControllerActivator
针对请求对目标HttpController的激活机制最终落实到一个名为HttpControllerActivator的对象上,所有的HttpControllerActivator类型均实现了IHttpControllerActivator接口。如下面的代码片断所示,定义其中的唯一方法Create会根据表示当前请求的HttpRequestMessage对象、描述目标HttpController的HttpControllerDescriptor对象以及目标HttpController的类型来创建对应的HttpController对象。
1: public interface IHttpControllerActivator
2: {
3: IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType);
4: }
我们已经知道了像这样的“标准化组件”一定是注册到当前ServicesContainer上被HttpController激活系统使用的。我们可以通过ServicesContainer具有如下定义的扩展方法GetHttpControllerActivator直接获取注册的HttpControllerActivator对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerActivator GetHttpControllerActivator(this ServicesContainer services);
5: }
实际上HttpControllerDescriptor的CreateController方法就是调用这个扩展方法得到注册的HttpControllerActivator对象,并调用它的Create方法来创建目标HttpController的。如下的代码体现了CreateController方法真正的实现逻辑。
1: public class HttpControllerDescriptor
2: {
3: //其他成员
4: public virtual IHttpController CreateController(HttpRequestMessage request)
5: {
6: return this.Configuration.Services.GetHttpControllerActivator().Create(request, this, this.ControllerType);
7: }
8: }
2、DefaultHttpControllerActivator
我们照例利用通过DefaultServices的构造函数定义分析出默认注册的HttpControllerActivator是个怎样的对象。如下面的代码片断所示,当DefaultServices被初始化的时候它会创建并注册一个类型为DefaultHttpControllerActivator对象。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerActivator>(new DefaultHttpControllerActivator());
8: }
9: }
3、DependencyResolver
1: public interface IDependencyResolver : IDependencyScope, IDisposable
2: {
3: IDependencyScope BeginScope();
4: }
5:
6: public interface IDependencyScope : IDisposable
7: {
8: object GetService(Type serviceType);
9: IEnumerable<object> GetServices(Type serviceType);
10: }
通过DependencyResolver的BeginScope方法创建的IDependencyScope对象可以视为一个用于激活目标对象的上下文,我们可以通过调用它的GetService和GetServices方法根据指定的“服务接口类型”获取对应的服务实例。由于IDependencyScope继承自IDisposable,所以与此上下文关联的资源释放工作可以通过实现的Dispose方法来完成。
与上面我们介绍的那些“标准化组件”不同,默认使用的DependencyResolver并未注册到当前的ServicesContainer对象上,而是直接注册到了当前HttpConfiguration上面。如下面的代码片断所示,当前使用的DependencyResolver直接通过HttpConfiguration的DependencyResolver属性来获取和设置。
1: public class HttpConfiguration : IDisposable
2: {
3: //其他成员
4: public HttpConfiguration(HttpRouteCollection routes)
5: {
6: this._dependencyResolver = EmptyResolver.Instance;
7: }
8:
9: public IDependencyResolver DependencyResolver
10: {
11: get
12: {
13: return this._dependencyResolver;
14: }
15: set
16: {
17: this._dependencyResolver = value;
18: }
19: }
20: }
从上面的代码片断我们还可以看出默认注册到HttpConfiguration上的DependencyResolver是通过类型EmptyResolver的静态属性Instance返回的EmptyResolver对象。EmptyResolver是一个定义在程序集“System.Web.Http.dll”中的内部类型,其成员定义如下。之所以将它如此命名,原因在于它仅仅是一个“空”的IoC容器。它的BeginScope返回的是它自身,GetService和GetServices方法分别返回Null和一个空对象集合,Dispose方法也没有任何资源释放工作要做。
1: internal class EmptyResolver : IDependencyResolver, IDependencyScope,
2: IDisposable
3: {
4: public IDependencyScope BeginScope();
5: public void Dispose();
6: public object GetService(Type serviceType);
7: public IEnumerable<object> GetServices(Type serviceType);
8: public static IDependencyResolver Instance { get; }
9: }
4、HttpRequestMessage中的DependencyResolver
虽然当前使用的DependencyResolver是注册到当前HttpConfiguration上的,但是我们可以直接从表示当前请求的HttpRequestMessage对象中获取由它创建的DependencyScope对象。如下面的代码片断所示,HttpRequestMessage具有一个返回类型为IDependencyScope接口的扩展方法GetDependencyScope。
1: public static class HttpRequestMessageExtensions
2: {
3: //其他成员
4: public static IDependencyScope GetDependencyScope(this HttpRequestMessage request);
5: }
其实这个扩展方法实现逻辑很简单,因为DependencyScope对象也存放于HttpRequestMessage的属性字典中。如果此DependencyScope对象尚未添加,该方法则会通过当前的HttpConfiguration得到注册的DependencyResolver对象,然后利用它创建一个新的DependencyScope对象并添加到HttpRequestMessage对象的属性字典中,后续过程如果需要使用到此DependencyScope就可以直接从HttpRequestMessage中提取了。
5、DependencyResolver在DefaultHttpControllerActivator中的应用
在对DependencyResolver有了基本了解后,我们再来讨论DefaultHttpControllerActivator的Create方法是如何根据当前请求来激活目标HttpController对象的。其实实现机制非常简单,DefaultHttpControllerActivator先通过调用表示当前请求的HttpRequestMessage对象的扩展方法GetDependencyScope得到通过当前DependencyResolver创建的DependencyScope对象,然后将目标HttpController的类型作为参数调用其GetService方法。如果该方法返回一个具体的HttpController对象,该对象就是Create方法的返回值,否则直接根据目标HttpController的类型进行反射创建一个HttpController对象并返回。如下所示的代码片断基本上体现了DefaultHttpControllerActivator的HttpController激活机制。
1: public class DefaultHttpControllerActivator : IHttpControllerActivator
2: {
3: public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
4: {
5: IDependencyScope depedencyScope = request.GetDependencyScope();
6: object httpController = depedencyScope.GetService(controllerType)?? Activator.CreateInstance(controllerType);
7: return httpController as IHttpController;
8: }
9: }
由于默认请求下注册到当前HttpConfiguration上的DependencyResolver是一个EmptyResolver对象,它的GetService方法总是返回Null,所以默认情况下对HttpController的激活总是利用针对目标HttpController类型的反射实现的。关于HttpController的激活,我还想强调一点,在默认情况下解析出来的所有HttpController类型会被缓存,创建的用于描述HttpController的HttpControllerDescriptor对象也会被缓存,但是HttpController激活系统并不会对创建的HttpController对象实施缓存。换言之,对于多个针对相同的HttpController类型的请求来说,最终被激活的HttpController实例都是不同的。
ASP.NET Web API的Controller是如何被创建的?相关推荐
- 【ASP.NET Web API教程】2.3.3 创建Admin控制器
原文:[ASP.NET Web API教程]2.3.3 创建Admin控制器 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. Part 3 ...
- ASP.NET Web API中的Controller
虽然通过Visual Studio向导在ASP.NET Web API项目中创建的 Controller类型默认派生与抽象类型ApiController,但是ASP.NET Web API框架本身只要 ...
- 监控系统简介(二):使用 App Metrics 在 ASP.NET Web API 中记录指标
回顾 在<监控系统简介:使用 Prometheus 与 Grafana>一文中,我们了解了什么是监控系统,Prometheus 这一监控工具及它提供的数据类型.PromQL 以及 Graf ...
- Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)...
原文:Asp.Net Web API 2第十七课--Creating an OData Endpoint in ASP.NET Web API 2(OData终结点) 前言 很久没更新博客了,加上刚过 ...
- ASP.NET Web API自身对CORS的支持:从实例开始
在<通过扩展让ASP.NET Web API支持W3C的CORS规范>中我们通过自定义的HttpMessageHandler为ASP.NET Web API赋予了跨域资源共享的能力,具体来 ...
- ASP.NET Web API实践系列06, 在ASP.NET MVC 4 基础上增加使用ASP.NET WEB API
本篇尝试在现有的ASP.NET MVC 4 项目上增加使用ASP.NET Web API. 新建项目,选择"ASP.NET MVC 4 Web应用程序". 选择"基本&q ...
- [转]Web API Introduction to OData Services using ASP.NET Web API
本文转自:http://mahedee.net/tag/web-api/ What is OData? OData Stands for Open Data Protocol. It is a dat ...
- ASP.NET Web API 路由对象介绍
前言 在ASP.NET.ASP.NET MVC和ASP.NET Web API这些框架中都会发现有路由的身影,它们的原理都差不多,只不过在不同的环境下作了一些微小的修改,这也是根据每个框架的特性来制定 ...
- ASP.NET Web API 过滤器创建、执行过程(二)
ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ...
最新文章
- 博士补贴125万,硕士70万本科21万,浙江某地人才(简直是抢人)新政!
- Xamarin Android长度单位区别
- optee对std smc的处理的详解
- 美团王庆:当老板对指标进行灵魂拷问时,该如何诊断分析?
- ITK:使用均方执行二维平移配准
- 仿豆丁百度文库网页版阅读器完整解决方案
- linux驱动程序混合架构,嵌入式系统最小驱动框架(类似linux驱动程序架构)(示例代码)...
- LeetCode【3--无重复的最长字串】 LeetCode【4--有序数组中的中位数】
- MyBatis之使用resultMap实现高级映射
- 给你的网站添加3D地球显示的访客统计(使用RevolverMaps)
- 5G学习-3GPP协议入门
- 《华为时间管理培训》笔记
- MINIST数据集读取
- android nano app,实战nanoHTTPD嵌入android app(3)
- 个股分析|巴菲特炒银行股的5个原因
- 控制台、操作台、调度台如何区分?
- OkHttp实现远程调用
- 微信网页端软键盘收起点击事件失效
- 用马青公式计算圆周率,Python语言
- 手动搭建高可用的Redis5.0分片集群,从理论到实践,超详细