惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
Y
Y Combinator Blog
博客园 - 聂微东
GbyAI
GbyAI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
月光博客
月光博客
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
雷峰网
雷峰网
MyScale Blog
MyScale Blog
Cloudbric
Cloudbric
T
Threatpost
Scott Helme
Scott Helme
N
News | PayPal Newsroom
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Engineering at Meta
Engineering at Meta
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
V2EX - 技术
V2EX - 技术
罗磊的独立博客
Attack and Defense Labs
Attack and Defense Labs
IT之家
IT之家
S
Secure Thoughts
C
Cyber Attacks, Cyber Crime and Cyber Security
D
Docker
V
V2EX
T
Troy Hunt's Blog
Forbes - Security
Forbes - Security
aimingoo的专栏
aimingoo的专栏
J
Java Code Geeks
Last Week in AI
Last Week in AI
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
P
Proofpoint News Feed
Recorded Future
Recorded Future
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
O
OpenAI News
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
云风的 BLOG
云风的 BLOG

博客园 - Artech

[MAF预定义的AIContextProvider-05]CompactionProvider——采用多种策略压缩对话历史 [MAF预定义的AIContextProvider-04]Mem0Provider——长期记忆基于的云端解决方案 [MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力 [MAF预定义的AIContextProvider-02]AgentSkillsProvider——将Agent Skills引入MAF [MAF预定义的AIContextProvider-01]TextSearchProvider——RAG在MAF中的实现 [MAF预定义ChatClient中间件-09]MessageInjectingChatClient-赋予工具消息注入的能力 [MAF预定义ChatClient中间件-08]OpenTelemetryChatClient-实现链路跟踪和性能监控 [MAF预定义ChatClient中间件-07]PerServiceCallChatHistoryPersistingChatClient——基于ReAct循环的一步一存档 [MAF预定义ChatClient中间件-06]利用ImageGeneratingChatClient开发专业图片生成Agent [MAF预定义ChatClient中间件-05]动态修改ChatOptions和请求消息 [MAF预定义ChatClient中间件-04]ReducingChatClient——精减对话历史又不丢失基本语义 [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间 [MAF预定义ChatClient中间件-02]FunctionInvokingChatClient——实现ReAct循环和人机交互的大功臣 [MAF预定义ChatClient中间件-01]LoggingChatClient——在调用LLM前后输出日志 [MAF的Agent管道详解-07]利用AIAgent中间件构建Agent管道 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 [MAF的Agent管道详解-06]ChatClientAgent对IChatClient和输入输出增强管道的整合 - Artech [MAF的Agent管道详解-05]对话历史的持久化和输入输出的增强 [MAF的Agent管道详解-04]如何让LLM按照要求的结构输出数据? [MAF的Agent管道详解-03]连接LLM的IChatClient对象 [MAF的Agent管道详解-02]IChatClient管道如何完美连接大模型? [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 [对比学习LangChain和MAF-04]针对消息的设计 [对比学习LangChain和MAF-03]完全不同的Agent设计哲学 [对比学习LangChain和MAF-02]基本编程模式的差异(下篇) [对比学习LangChain和MAF-01]基本编程模式的差异(上篇) 这是一篇测试文章 我所理解的Python元模型 除了按值和引用,方法参数的第三种传递方式 方法的三种调用形式 可以调用Null的实例方法吗? 自定义Key类型的字典无法序列化的N种解决方案 为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate 深入解析ASP.NET Core MVC的模块化设计[下篇] 深入解析ASP.NET Core MVC的模块化设计[上篇] ASP.NET Core MVC应用模型的构建[3]: Controller的收集 ASP.NET Core MVC应用模型的构建[2]: 定制应用模型 ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图 基于HTTP2/3的流模式消息交换如何实现? 编写高效的代码,你应该了解Array、Memory、ReadOnlySequence . . . WebAssembly核心编程[4]: Memory WebAssembly核心编程[3]: Module 与 Instance WebAssembly核心编程[2]:类型系统 WebAssembly核心编程[1]:wasm模块实例化的N种方式 WebAssembly入门笔记[4]:利用Global传递全局变量 WebAssembly入门笔记[3]:利用Table传递引用
ASP.NET Core MVC应用模型的构建[4]: Action的选择
Artech · 2024-02-29 · via 博客园 - Artech

ControllerModel类型的Actions属性包含一组描述有效Action方法的ActionModel对象。对于定义在Controller类型中的所有方法,究竟哪些方法才能成为有效的Action方法呢?所以在正式介绍ActionModel类型之前,我们先来聊聊Action方法的选择规则。

一、Action方法的选择
二、ActionModel
三、PropertyModel与ParameterModel
四、SelectorModel
五、实例演示:选择器模型的构建

一、 Action方法的选择

当DefaultApplicationModelProvider对象根据某个具体的Controller类型创建对应ControllerModel对象的时候,它会提取出定义在该类型中的方法,并按照预定义的规则选择出有效的Action方法。有效的Action方法必须满足如下条件:

  • 公共方法:私有(Private)、内部(Internal)和受保护(Protected)方法均为无效Action方法。
  • 非抽象方法:抽象方法为无效的Action方法(这个限制可以忽略,因为Controller不会是抽象类型)。
  • 非泛型方法:Action方法不能定义成泛型方法。
  • 非静态方法:静态方法为无效Action方法。
  • 不是从Object类型上继承的方法:Action方法支持继承,但是从Object类型上继承的方法不能成为Action方法。
  • 不是对IDisposable接口的实现:如果Controller类型实现了IDisposable接口,实现的Dispose方法不是有效的Action方法。

二、ActionModel

如下面的代码片段所示,ActionModel类型实现了ICommonModel、IFilterModel和IApiExplorerModel三个接口。默认注册的DefaultApplicationModelProvider会对ActionModel对象做如下的设置:MemberInfo和ActionMethod属性都将设置为描述当前Action方法的MethodInfo对象。通过标注的特性注册到Action方法上的过滤器会被提取出来,对应的元数据会添加到Filters属性中。ApiExplorer属性返回的ApiExplorerModel对象由标注在Action方法上实现了IApiDescriptionGroupNameProvider和IApiDescriptionVisibilityProvider接口的特性构建而成。

public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel
{
    public ControllerModel 			Controller { get; set; }

    public IList<IFilterMetadata> 		Filters { get; }
    public ApiExplorerModel 			ApiExplorer { get; set; }
    public IDictionary<object, object> 	Properties { get; }

    public IList<ParameterModel> 		Parameters { get; }
    public IList<SelectorModel> 		Selectors { get; }

    public MethodInfo 				ActionMethod { get; }
    public string 				DisplayName { get; }
    public string 				ActionName { get; set; }
    public IReadOnlyList<object> 		Attributes { get; }

    public IOutboundParameterTransformer 	RouteParameterTransformer { get; set; }
    public IDictionary<string, string> 	RouteValues { get; }

    MemberInfo ICommonModel.MemberInfo { get; }
    string ICommonModel.Name { get; }
}

DefaultApplicationModelProvider会为Action方法的每个参数创建一个ParameterModel对象,并添加到Parameters属性中。应用在Action方法上的用于封装路由信息(特性路由、约束和终结点元数据)的SelectorModel对象会按照上述的方式构建出来,并添加到Selectors属性中。标注在Action方法上的特性会被提取出来并添加到Attributes属性返回的列表中。表示Action名称的ActionName与Name属性具有相同的值,DefaultApplicationModelProvider会默认将它们设置为方法的名称,但是我们可以在方法上通过标注如下这个ActionNameAttribute特性对该属性进行设置。

[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public sealed class ActionNameAttribute : Attribute
{
    public string Name { get; }
    public ActionNameAttribute(string name);
}

我们照例通过一个简单的示例来演示一下DefaultApplicationModelProvider对象针对ActionModel的构建规则。我们依然沿用前面创建的演示实例,这次我们将FoobarController定义成如下的形式。如代码片段所示,我们为Action方法定义了三个参数,并在上面标注了四个特性,其中FoobarAttribute特性是我们应用在该Action方法上的过滤器,ApiExplorerSettingsAttribute特性针对ApiExplorer作了相应设置,HttpGetAttribute特性完成了针对路由和HTTP方法约束的定义,ActionNameAttribute特性则将Action名称设置为“Baz”。

public class FoobarController
{
    [Foobar]
    [ApiExplorerSettings(GroupName ="test")]
    [HttpGet("/foobar/baz")]
    [ActionName("Baz")]
    public void Index(string foo, string bar, string baz) => throw new NotImplementedException();
}

我们需要修改定义在HomeController中的Action方法Index。如下面的代码片段所示,在利用通过参数注入的ApplicationModelProducer对象根据提供的FoobarController类型创建出对应ApplicationModel对象之后,我们提取出描述Action方法Index的ActionModel对象,并将其作为Model呈现在默认的View中。

public class HomeController : Controller
{
    [HttpGet("/")]
    public IActionResult Index([FromServices]ApplicationModelProducer producer)
    {
        var applicationModel = producer.Create(typeof(FoobarController));
        return View(applicationModel.Controllers.Single().Actions.Single());
    }
}

我们将按照如下的形式重新定义了Action方法Index对应的View。如下面的代码片段所示,这是一个Model类型为ActionModel的强类型View,,它将ActionModel承载的元数据呈现在一个表格中。

@model Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel
@{
    var filters 	= Model.Filters;
    var routeValues 	= Model.RouteValues.ToArray();
    var parameters 	= Model.Parameters;
    var attributes 	= Model.Attributes;
}
<html>
<head>
    <title>Action</title>
</head>
<body>
    <table border="1" cellpadding="0" cellspacing="0">
        <tr><td>Method</td><td>@Model.ActionMethod.Name</td></tr>
        <tr><td>ActionName</td><td>@Model.ActionName</td></tr>
        <tr><td>DisplayName</td><td>@Model.DisplayName</td></tr>
        <tr>
            <td rowspan="@parameters.Count">Parameters</td>
            <td>@parameters[0].Name</td>
        </tr>
        @for (int index = 1; index < parameters.Count; index++)
        {
            <tr><td>@parameters[index].Name</td></tr>
        }
        <tr>
            <td rowspan="@filters.Count">Filters</td>
            <td>@filters[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < filters.Count; index++)
        {
            <tr><td>@filters[index].GetType().Name</td></tr>
        }
        <tr>
            <td rowspan="@attributes.Count">Attributes</td>
            <td>@attributes[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < attributes.Count; index++)
        {
            <tr><td>@attributes[index].GetType().Name</td></tr>
        }
        @if (routeValues.Length == 0)
        {
            <tr><td>RouteValues</td><td>N/A</td></tr>
        }
        else
        {
            <tr>
                <td rowspan="@routeValues.Length">RouteValues</td>
                <td>@routeValues[0].Key = @routeValues[0].Value</td>
            </tr>
        }
        @for (int index = 1; index < routeValues.Length; index++)
        {
            <tr><td>@routeValues[index].Key = @routeValues[index].Value</td></tr>
        }
        <tr>
            <td rowspan="2">ApiExplorer</td>
            <td>IsVisible = @Model.ApiExplorer.IsVisible </td>
        </tr>
        <tr>
            <td>GroupName = @Model.ApiExplorer.GroupName </td>
        </tr>
    </table>
</body>
</html>

改动后的演示程序启动后,我们利用浏览器访问应用的主页,可以得到如图1所示的输出结果。我们从图中可以看出,Action名称来源于标注在方法上的ActionNameAttribute特性。DefaultApplicationModelProvider会为方法的每个参数创建一个ParameterModel对象并添加到ActionModel对象的Properties属性中。通过特性标注注册到Action方法上的FoobarAttribute过滤器被添加到ActionModel对象的Filters属性中。Action方法标注的四个特性全部被添加到ActionModel对象的Attributes属性中。ActionModel对象的ApiExplorer属性返回的ApiExplorerModel对象是由标注在方法上的ApiExplorerSettingsAttribute特性构建的。值得一提的是,Controller和Action的名称此时并没有作为路由参数添加到RouteValues属性中。

clip_image002

图1 Action模型默认的构建规则

三、PropertyModel与ParameterModel

默认注册的DefaultApplicationModelProvider会将定义在Controller类型的公共属性(包括从基类继承的属性)提取创建,然后创建相应的PropertyModel对象并添加到ControllerModel对象的ControllerProperties属性中。描述属性的PropertyModel对象和描述参数的ParameterModel对象都是为了提供模型绑定的元数据,所以它们具有相同的基类ParameterModelBase。

public abstract class ParameterModelBase : IBindingModel
{
    public string 				Name { get; protected set; }
    public Type 				ParameterType { get; }
    public IReadOnlyList<object> 		Attributes { get; }
    public BindingInfo 		 	        BindingInfo { get; set; }
    public IDictionary<object, object> 	Properties { get; }
}

如上面的代码片段所示,抽象类ParameterModelBase实现了IBindingModel,所以它需要利用实现的BindingInfo书信提供模型绑定信息。ParameterModelBase并没有实现IPropertyModel接口,但是其自身提供了一个Properties属性。ParameterModelBase的Name和ParameterType属性分别表示对应参数/属性的名称和类型。标注到属性上的特性会添加到Attributes属性中。如下所示的是描述Controller属性的PropertyModel类型,和描述Action方法参数的ParameterModel类型的定义。

public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel
{
    public ControllerModel 			Controller { get; set; }
    public PropertyInfo 			PropertyInfo { get; }
    public string 				PropertyName { get; set; }
    public IReadOnlyList<object> 		Attributes { get; }
    public IDictionary<object, object> 	Properties { get; }
    MemberInfo ICommonModel.MemberInfo { get; }
}

public class ParameterModel : ParameterModelBase, ICommonModel
{
    public ActionModel 			        Action { get; set; }
    public ParameterInfo 			ParameterInfo { get; }
    public string 				ParameterName { get; set; }
    public string 				DisplayName { get; }
    public IReadOnlyList<object> 		Attributes { get; }
    public IDictionary<object, object> 	Properties { get; }
    MemberInfo ICommonModel.MemberInfo { get; }
}

四、SelectorModel

SelectorModel类型是对Action选择器(Selector)的描述,这里的选择器旨在解决如何为请求选择匹配Action的问题,所以它承载的其实针对路由的原始定义。如下面的代码片段所示,SelectorModel类型通过AttributeRouteModel、ActionConstraints和EndpointMetadata分别存储了特性路由信息、约束和终结点元数据。

public class SelectorModel
{
    public AttributeRouteModel 		        AttributeRouteModel { get; set; }
    public IList<IActionConstraintMetadata> 	ActionConstraints { get; }
    public IList<object> 			EndpointMetadata { get; }
}

public class AttributeRouteModel
{
    public IRouteTemplateProvider 	Attribute { get; }
    public string 			Template { get; set; }
    public int? 			Order { get; set; }
    public string 			Name { get; set; }
    public bool 			SuppressLinkGeneration { get; set; }
    public bool 			SuppressPathMatching { get; set; }
    public bool 			IsAbsoluteTemplate { get; }
}

由于路由可以通过标注到Controller类型或者Action方法上相应的特性来定义,所以描述Controller类型和Action方法的ControllerModel和ActionModel类型都具有一个Selectors属性来保存各自的选择器,DefaultApplicationModelProvider针对它们的解析方式也是一致的。这里用来定义路由的特性实现了如下所示的IRouteTemplateProvider特性。

public interface IRouteTemplateProvider
{
    string 	Template { get; }
    int? 	Order { get; }
    string 	Name { get; }
}

顾名思义,实现IRouteTemplateProvider接口的特性旨在定义一个针对指定模板的路由。除此之外,针对SelectorModel的构建还涉及另一个名为IActionHttpMethodProvider的接口,实现该接口的特性为目标Action定义针对HTTP方法的约束。

public interface IActionHttpMethodProvider
{
    IEnumerable<string> HttpMethods { get; }
}

对于目前提供的实现了IActionHttpMethodProvider接口的特性来说,它们无一例外都同时实现了IRouteTemplateProvider接口。比如下面这个AcceptVerbsAttribute特性可以标注到Action方法上指定一组支持的HTTP方法,同时也可以利用三个属性(Route、Name和Order)对路由作相应的定义(显式实现的Template和Order属性与自身Route和Order属性具有相同的值)。顺便提一下,我们在AcceptVerbsAttribute特性构造函数以字符串指定的HTTP方法名称会一律转换成大写形式。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
    public IEnumerable<string> 	HttpMethods { get; }
    public string 			Route { get; set; }
    public int 			        Order { get; set; }
    public string 			Name { get; set; }

    string IRouteTemplateProvider.Template { get; }
    int? IRouteTemplateProvider.Order { get; }

    public AcceptVerbsAttribute(string method);
    public AcceptVerbsAttribute(params string[] methods);
}

虽然AcceptVerbsAttribute特性可以为我们指定多个支持的HTTP方法,但是我们似乎更倾向于使用针对具有某种HTTP方法的特性,比如HttpGetAttribute、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute、HttpHeadAttribute、HttpPatchAttribute和HttpOptionsAttribute特性,它们都派生于如下这个抽象的HttpMethodAttribute特性类型。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public abstract class HttpMethodAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
    public IEnumerable<string> 	HttpMethods { get; }
    public string 			Template { get; }
    public int 			        Order { get; set; }
    public string 			Name { get; set; }

    int? IRouteTemplateProvider.Order { get; }

    public HttpMethodAttribute(IEnumerable<string> httpMethods);
    public HttpMethodAttribute(IEnumerable<string> httpMethods, string template);
}

IRouteTemplateProvider接口除了上述这些实现类型之外,如下这个专门用来定义路由的RouteAttribute特性单独实现了该接口。从提供的代码片段可以看出,RouteAttribute特性可以标注在Controller类型或者具体的Action方法上。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public class RouteAttribute : Attribute, IRouteTemplateProvider
{
    public string 		Template { get; }
    public int 		        Order { get; set; }
    public string 		Name { get; set; }

    public RouteAttribute(string template);
    int? IRouteTemplateProvider.Order { get; }
}

DefaultApplicationModelProvider针对SelectorModel对象的构建逻辑分为两种情况。如果标注在当前Controller类型或者Action方法上的特性没有提供任何路由信息,这种情况由分为两种场景:其一,根本没有标注任何实现了IRouteTemplateProvider接口的特性;其二,所有的特性并没有对定义在该接口的三个属性(Template、Name和Order)做任何设置,比如我们经常在Action方法上标注一个没有提供任何参数的HttpGetAttribute特性,其目的只是限制支持的HTTP方法,而不是定义路由。

在这种情况下,DefaultApplicationModelProvider会创建一个唯一的SelectorModel对象。由于没有任何路由被定义,所以该对象的AttributeRouteModel属性会返回Null。标注在Controller类型或者Action方法上实现了IActionConstraintMetadata接口的特性会作为约束添加到ActionConstraints属性中。如果标注了实现IActionHttpMethodProvider接口的特性对HTTP方法做了限制,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。除此之外,所有特性都会作为终结点元数据添加到EndpointMetadata属性中。

如果当前Controller类型或者Action方法上标注了实现IRouteTemplateProvider接口的特性,并且作了具体的路由设置,DefaultApplicationModelProvider会为每个IRouteTemplateProvider对象创建一个SelectorModel对象。对于每一个针对IRouteTemplateProvider对象创建的SelectorModel对象来说,设置的路由信息会被提取出来用于创建对应的AttributeRouteModel对象。如果当前特性是一个IActionHttpMethodProvider对象,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来,当前特性和这个对象都将作为终结点元数据被添加到EndpointMetadata属性中。

如果当前IRouteTemplateProvider对象类型为RouteAttribute,那些没有提供路由信息的实现了IActionHttpMethodProvider接口的特性(即只定义了HTTP方法约束的特性)会被提取出来,一个根据它们提供的HTTP方法列表创建的HttpMethodActionConstraint对象并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。EndpointMetadata属性最终包含的终结点元数据还包括当前RouteAttribute特性和这些单纯定义约束的特性。

五、实例演示:选择器模型的构建

对于DefaultApplicationModelProvider为Controller类型或者Action方法构建SelectorModel的逻辑,我想针对具体的场景会更好理解一点,为此我们来演示几个简单的实例。我们依然沿用前面的演示程序,并将FoobarController类型改写成如下的形式。FoobarController类型中定义了三个Action方法,接下来我们看看DefaultApplicationModelProvider对象会为它们创建出怎样的选择器。

public class FoobarController
{
    [HttpGet]
    [HttpPost]
    public void Foo()=>throw new NotImplementedException();

    [HttpGet("bar")]
    [HttpPost("bar")]
    public void Bar() => throw new NotImplementedException();

    [HttpGet()]
    [HttpPost("bar")]
    [Route("bar")]
    public void Baz() => throw new NotImplementedException();
}

我们对定义在HomeController中的Action方法Index作如下的修改。如代码片段所示,在该方法中,我们利用通过参数注入的ApplicationModelProducer对象根据FoobarController类型创建出对应的ApplicationModel对象,然后利用查询字符串绑定的actionName参数提取出描述对应Action的ActionModel对象。我们将ActionModel对象的Selectors属性提取的选择器列表作为Model呈现在View中。

public class HomeController : Controller
{
    [HttpGet("/")]
    public IActionResult Index([FromServices]ApplicationModelProducer producer, [FromQuery]string actionName)
    {
        var applicationModel = producer.Create(typeof(FoobarController));
        var actionModel = applicationModel.Controllers.Single().Actions.Single(it=>string.Compare(it.ActionName, actionName, true) == 0);
        return View(actionModel.Selectors);
    }
}

如下所示的是修改后的View。如代码片段所示,这个是一个Model类型为IList<SelectorModel>的强类型View,我们将提供的用于描述选择器的每个SelectorModel对象的元数据信息呈现在表格中。

@using Microsoft.AspNetCore.Mvc.ApplicationModels;
@model IList<SelectorModel>
<html>
<head>
    <title>Selectors</title>
</head>
<body>
    <table border="1" cellpadding="0" cellspacing="0">
        @for (int i = 0; i < Model.Count; i++)
        {
            var selector = Model[i];
            var constraints = selector.ActionConstraints;
            var metadata = selector.EndpointMetadata;
            <tr><td colspan="2">Selector @(i+1)</td></tr>
            <tr>
                <td>AttributeRouteModel</td>
                <td>@selector.AttributeRouteModel?.Template</td>
            </tr>
            <tr>
                <td rowspan="@constraints.Count">ActionConstraints</td>
                <td>@constraints[0].GetType().Name</td>
            </tr>
            @for (int j = 1; j < constraints.Count; j++)
            {
                <tr><td>@constraints[j].GetType().Name</td></tr>
            }
            <tr>
                <td rowspan="@metadata.Count">EndpointMetadata</td>
                <td>@metadata[0].GetType().Name</td>
            </tr>
            @for (int j = 1; j < metadata.Count; j++)
            {
                <tr><td>@metadata[j].GetType().Name</td></tr>
            }
        }
    </table>
</body>
</html>

由于第一个Action方法Foo上的两个IActionHttpMethodProvider特性并没有提供任何的路由信息,所以它只具有一个AttributeRouteModel属性为Null的SelectorModel对象。这两个特性提供的针对HTTP方法(GET和POST)的约束会转换成一个HttpMethodActionConstraint对象并添加到SelectorModel对象的ActionConstraints属性中。除此之外,这两个特性会直接作为终结点元数据被添加到SelectorModel对象的EndpointMetadata属性中,该属性还会包含一个针对HTTP方法约束的HttpMethodMetada对象。图2所示的就是演示应用返回的针对Action方法Foo的选择器信息。

clip_image004

图2 Action方法Foo的选择器

第二个Action方法Bar上的两个特性均指定了路由模板,所以DefaultApplicationModelProvider会为它创建两个针对性的SelectorModel对象。DefaultApplicationModelProvider会根据特性(HttpGetAttribute和HttpPostAttribute)提供的路由信息来创建对应的AttributeRouteModel对象。SelectorModel对象ActionConstraints属性会包含根据各自提供的HTTP方法约束创建的HttpMethodActionConstraint对象。EndpointMetadata属性将会包含两个终结点元数据对象,分别是当前的特性和根据HTTP方法约束创建的HttpMethodMetada对象。图3所示的就是演示应用返回的针对Action方法Bar的选择器列表。

clip_image006

图3  Action方法Bar的选择器

第三个Action方法方法上标注了三个特性,但是其中只有两个特性提供了路由信息,所以DefaultApplicationModelProvider最终会根据标注的HttpPostAttribute和RouteAttribute特性创建出两个对应的SelectorModel对象。根据标注的HttpPostAttribute特性针对SelectorModel对象的创建与上面一致,所以我们现在只关注针对RouteAttribute特性创建的SelectorModel对象。该对象提供的AttributeRouteModel对象自然由RouteAttribute特性提供的路由信息来创建。

该方法上没有提供路由信息的HttpGetAttribute特性将被用来提供当前路由的约束,所以这个SelectorModel对象的ActionConstraints属性中会包含一个根据这个特性创建的HttpMethodActionConstraint对象。这个SelectorModel对象的EndpointMetadata属性中最终会包含三个终结点元数据,分别是标注的RouteAttribute和HttpGetAttribute特性,以及根据HTTP方法约束创建的HttpMethodMetada对象。图4所示的就是演示应用返回的针对Action方法Baz的选择器列表。

clip_image008

图4  Action方法Bar的选择器

ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图
ASP.NET Core MVC应用模型的构建[2]: 应用模型
ASP.NET Core MVC应用模型的构建[3]: Controller模型
ASP.NET Core MVC应用模型的构建[4]: Action模型