我个人觉得这是ASP.NET Core MVC框架体系最核心的部分。原因很简单,MVC框架建立在ASP.NET Core路由终结点上,它最终的目的就是将每个Action方法映射为一个或者多个路由终结点,路由终结点根据附加在Action上的若干元数据构建而成。为了构建描述当前应用所有Action的元数据,MVC框架会提取出定义在当前应用范围内的所有Controller类型,并进一步构建出基于Controller的应用模型。应用模型不仅仅是构建Action元数据的基础,承载API的应用还可以利用它自动生成API开发文档,一些工具甚至可以利用应用模型自动生成消费API的客户端代码。这篇文章大概是两年之前写的,可能一些技术细节在最新版本的ASP.NET Core MVC已经发生了改变,但总体设计依然如此。

不论是面向Controller的MVC编程模型,还是面向页面的Razor Pages编程模型,客户端请求访问的目标都是某个Action,所以MVC框架的核心功能就是将请求路由到正确的Action,并通过执行目标Action的方式完成请求当前请求的处理。目标Action应该如何执行由描述它的元数据来决定,而这样的元数据是通过ApplicationModel类型标识的应用模型构建出来的。应用模型为MVC应用构建了一个基于Controller的蓝图,我们先从宏观的角度来看看这张蓝图是如何绘制的。

一、 总体设计

二、ApplicationModel

三、IApplicationModelProvider

四、IApplicationModelConvention

五、其他约定

六、ApplicationModelFactory

一、 总体设计

图1基本体现了MVC框架构建应用模型的总体设计。代表使用模型的ApplicationModel对象是通过作为工厂的ApplicationModelFactory对象构建的,但是具体的构建任务却落在注册的一系列IApplicationModelProvider和IApplicationModelConvention对象上。

图1 ApplicationModel的构建模型

具体来说,ApplicationModelFactory工程会先创建一个空的ApplicationModel对象,并利用注册的IApplicationModelProvider对象对这个对象进行完善和修正。在此之后,代表默认约定的一系列IApplicationModelConvention对象会依次被执行,它们会将针对应用模型的约定规则应用到同一个ApplicationModel对象上。经过这两个加工环节之后得到的ApplicationModel最终成为描述应用模型的蓝图。

二、ApplicationModel

表示应用模型的ApplicationModel对象不仅是常见Action元数据的依据,同时还有其他重要的用途。由于ApplicationModel对象绘制了整个应用的蓝图,我们经常不仅可以利用它来生成结构化API文档(比如Swagger),还可以利用它提供的元数据生成调用API的客户端代码。通过ApplicationModel表示的应用模型总体上具有如图2所示的结构:一个ApplicationModel对象包含多个描述Controller的ControllerModel对象,一个ControllerModel包含多个ActionModel和PropertyModel对象,ActionModel和PropertyModel是对定义在Controller类型中的Action方法和属性的描述。表示Action方法的ActionModel对象利用ParameterModel描述其参数。

图2 应用模型总体结构

三、IApplicationModelProvider

在软件设计中我们经常会遇到这样的场景:我们需要构建一个由若干不同元素组成的复合对象,不同的组成元素具有不同的构建方式,MVC框架几乎基于采用了同一种模式来处理这样的场景。举个简单的例子:对象Foo需要实现的功能需要委托一组Bar对象来实现。MVC框架针对这种需求大都采用如图3所示模式来实现:Foo先创建一个上下文,并提供必要的输入,然后驱动每个Bar对象在这个上下文中完成各自的处理任务。所有Bar对象针对数据和状态的修改,以及产生的输出均体现在这个共享的上下文中,所有对象最终通过这个上下文就可以得到应有的状态或者所需的输出。

图3 基于共享上下文的多对象协作模式(单操作)

有时候我们甚至可以将Bar对象的操作分成两个步骤进行,比如我们将针对这两个步骤的操作分别命名为Executing和Executed。如图4所示,在创建共享上下文之后,Foo对象先按序执行每一个Bar对象的Executing操作,最后再反向执行每个Bar对象的Executed操作,所有的操作均在同一个上下文中执行。

图4 基于共享上下文的多对象协作模式(两阶段)

了解了上面所述的基于共享上下文的多对象协作对象构建模式之后,读者朋友们对于IApplicationModelProvider接口定义就很好理解了。如下面的代码片段所示,IApplicationModelProvider接口定了Order属性来决定了自身的执行顺序,而OnProvidersExecuting和OnProvidersExecuted方法分别完成针对Action元数据构建的两阶段任务。

public interface IApplicationModelProvider
{
int Order { get; }
void OnProvidersExecuted(ApplicationModelProviderContext context);
void OnProvidersExecuting(ApplicationModelProviderContext context);
}

这里作为构建应用模型的执行上下文通过如下这个ApplicationModelProviderContext类型表示。如代码片段所示,ApplicationModelProviderContext类型定义了两个属性,其中ControllerTypes属性表示的列表提供了当前应用所有有效的Controller类型,而Result属性返回的ApplicationModel对象自然代表“待改造”的应用模型。

public class ApplicationModelProviderContext
{
public IEnumerable<TypeInfo> ControllerTypes { get; }
public ApplicationModel Result { get; } public ApplicationModelProviderContext(IEnumerable<TypeInfo> controllerTypes);
}

MVC框架提供如下所示的几个针对IApplicationModelProvider接口的实现类型。对于最终用于描绘当前MVC应用的ApplicationModel对象,其承载的元数据绝大部分是由DefaultApplicationModelProvider对象提供的。AuthorizationApplicationModelProvider和CorsApplicationModelProvider主要提供针对授权和。而ApiBehaviorApplicationModelProvider则负责提供与API相关的描述信息。这些具体实现类型都是内部类型。

  • DefaultApplicationModelProvider:提供构成应用模型的绝大部分元数据。
  • AuthorizationApplicationModelProvider:提供与授权相关元数据。
  • CorsApplicationModelProvider:提供与跨域资源共享(CORS)相关的元数据。
  • ApiBehaviorApplicationModelProvider:提供与API行为相关的元数据
  • TempDataApplicationModelProvider:为定义在Controller类型中标注了TempDataAttribute特性的属性提供与临时数据保存相关的元数据。
  • ViewDataAttributeApplicationModelProvider:为定义在Controller类型中标注了ViewDataAttribute特性的属性提供与视图数据保存相关的元数据。

IApplicationModelProvider对象针对应用模型的构建是通过ApplicationModelFactory工厂驱动实施的,供这个工厂对象驱策的IApplicationModelProvider对象只需要预先注册到依赖注入容器框架即可。为MVC框架注册基础服务的AddMvcCore扩展方法具有针对DefaultApplicationModelProvider和ApiBehaviorApplicationModelProvider类型以及ApplicationModelFactory的服务注册。IServiceCollection接口的AddControllers扩展方法会添加针对AuthorizationApplicationModelProvider和 CorsApplicationModelProvider类型的注册。针对TempDataApplicationModelProvider ViewDataAttributeApplicationModelProvider类型的服务注册是在IServiceCollection接口的AddControllersWithViews扩展方法中被注册的。

四、IApplicationModelConvention

除了通过在依赖注入框架中注册自定义的IApplicationModelProvider实现类型或者对象方式来定制最终生成的应用模型之外,相同的功能还可以通过注册相应的IApplicationModelConvention对象来完成。顾名思义,IApplicationModelConvention对象旨在帮助我们为应用模型设置一些基于约定的元数据。如下面的代码片段所示,IApplicationModelConvention接口定义了唯一的Apply方法将实现在该方法的约定应用到指定的ApplicationModel对象上。

public interface IApplicationModelConvention
{
void Apply(ApplicationModel application);
}

与IApplicationModelProvider对象或者实现类型的注册不同,供ApplicationModelFactory工厂使用的IApplicationModelConvention对象需要注册到作为MVC应用配置选项的MvcOptions对象上。具体来说,我们需要将注册的IApplicationModelConvention对象添加到MvcOptions如下所示的Conventions属性上。

public class MvcOptions : IEnumerable<ICompatibilitySwitch>
{
public IList<IApplicationModelConvention> Conventions { get; }
}

五、其他约定

除了利用自定义的IApplicationModelConvention实现类型对整个应用模型进行定制之外,我们还可以针组成应用模型的某种“节点类型”(Controller类型、Action方法、方法参数等)定义相应的约定,这些约定都具有相应的接口。应用模型分别利用ControllerModel、ActionModel和ParameterModel类型来描述Controller类型、Action方法以及方法参数。我们可以分别实现如下的接口定义相应特性,并将它们分别标注到Controller类型、Action方法或者方法参数上,ApplicationModelFactory对象会自动提取这些特性并将它们提供的约定应用到对应类型的模型节点上。

public interface IControllerModelConvention
{
void Apply(ControllerModel controller);
} public interface IActionModelConvention
{
void Apply(ActionModel action);
} public interface IParameterModelConvention
{
void Apply(ParameterModel parameter);
}

描述Controller类型属性的PropertyModel类型的最终目的是为了能够采用模型绑定的方式来完整针对对应属性的绑定,这与针对Action方法参数的绑定是一致的,所以PropertyModel和描述Action方法参数的ParameterModel类型具有相同的基类ParameterModelBase。为了定制Controller类型属性和Action方法参数类型的应用模型节点,MVC框架为我们定义了如下这个IParameterModelBaseConvention接口。

public interface IParameterModelBaseConvention
{
void Apply(ParameterModelBase parameter);
}

我们可以和上面一样将实现类型定义成标注到属性和参数上特性,也可以让实现类型同时也实现IApplicationModelConvention接口。值得一提的是,MVC框架并没有提供一个针对PropertyModel类型的IPropertyModelConvention接口,针对Action方法参数的IParameterModelConvention接口和IParameterModelBaseConvention接口之间也不存在继承关系。

六、ApplicationModelFactory

如下所示的是作为应用模型创建工厂的ApplicationModelFactory类型的定义。如代码片段所示,ApplicationModelFactory是一个内部类型。ApplicationModelFactory利用在构造函数中注入的参数得到所有注册的IApplicationModelProvider和IApplicationModelConvention对象。

internal class ApplicationModelFactory
{
private readonly IApplicationModelProvider[] _providers;
private readonly IList<IApplicationModelConvention> _conventions; public ApplicationModelFactory(IEnumerable<IApplicationModelProvider> providers, IOptions<MvcOptions> options)
{
_providers = providers.OrderBy(it => it.Order).ToArray();
_conventions = options.Value.Conventions;
} public ApplicationModel CreateApplicationModel(IEnumerable<TypeInfo> controllerTypes)
{
var context = new ApplicationModelProviderContext(controllerTypes);
for (var index = 0; index < _providers.Length; index++)
{
_providers[index].OnProvidersExecuting(context);
}
for (int index = _providers.Length - 1; index >= 0; index--)
{
_providers[index].OnProvidersExecuted(context);
}
ApplicationModelConventions.ApplyConventions(context.Result, _conventions);
return context.Result;
}
}

ApplicationModelFactory针对应用模型的构建体现在它的CreateApplicationModel方法上。如上面的代码片段所示,ApplicationModelFactory对象先根据提供的Controller类型列表创建出一个ApplicationModelProviderContext上下文对象。接下来,ApplicationModelFactory将这个上下文作为参数,按照Order属性确定的顺序调用每个IApplicationModelProvider对象的OnProvidersExecuting方法,然后再逆序调用它们的OnProvidersExecuted方法。ApplicationModelFactory最后会将通过所有IApplicationModelProvider对象参与构建的ApplicationModel从ApplicationModelProviderContext上下文中提取出来,并将各种方式注册的约定应用在该对象上,具体的实现体现在如下这个ApplyConventions方法上。

internal static class ApplicationModelConventions
{
public static void ApplyConventions(ApplicationModel applicationModel, IEnumerable<IApplicationModelConvention> conventions)
{
foreach (var convention in conventions)
{
convention.Apply(applicationModel);
} var controllers = applicationModel.Controllers.ToArray();
foreach (var controller in controllers)
{
var controllerConventions = controller.Attributes.OfType<IControllerModelConvention>().ToArray(); foreach (var controllerConvention in controllerConventions)
{
controllerConvention.Apply(controller);
} var actions = controller.Actions.ToArray();
foreach (var action in actions)
{
var actionConventions = action.Attributes.OfType<IActionModelConvention>().ToArray(); foreach (var actionConvention in actionConventions)
{
actionConvention.Apply(action);
} var parameters = action.Parameters.ToArray();
foreach (var parameter in parameters)
{
var parameterConventions = parameter.Attributes.OfType<IParameterModelConvention>().ToArray(); foreach (var parameterConvention in parameterConventions)
{
parameterConvention.Apply(parameter);
} var parameterBaseConventions = GetConventions<IParameterModelBaseConvention>(conventions, parameter.Attributes);
foreach (var parameterConvention in parameterBaseConventions)
{
parameterConvention.Apply(parameter);
}
}
} var properties = controller.ControllerProperties.ToArray();
foreach (var property in properties)
{
var parameterBaseConventions = GetConventions<IParameterModelBaseConvention>(conventions, property.Attributes); foreach (var parameterConvention in parameterBaseConventions)
{
parameterConvention.Apply(property);
}
}
}
} private static IEnumerable<TConvention> GetConventions<TConvention>(IEnumerable<IApplicationModelConvention> conventions, IReadOnlyList<object> attributes)
{
return Enumerable.Concat(conventions.OfType<TConvention>(), attributes.OfType<TConvention>());
}
}

如上面的代码片段所示,注册在MvcOptions配置选项上的IApplicationModelConvention对象提供的约定会直接应用到ApplicationModel对象上。除此之外,Controller类型、Action方法和方法参数上标注的相应约定特性会被提取出来,它们承载的约定规则会分别应用到对应的ControllerModel、ActionModel和ParameterModel对象上。

对于表示Action方法参数的ParameterModel对象和表示Controller类型属性的ProperrtyModel对象来说,应用在对应参数和属性上实现了IParameterModelBaseConvention接口的特性,以及同时实现了IParameterModelBaseConvention接口的IApplicationModelConvention对象,会被提取出来并将它们承载的约定应用到对应的参数或者属性节点上。

ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图的更多相关文章

  1. ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  2. ASP.NET Core MVC 之模型(Model)

    1.模型绑定 ASP.NET Core MVC 中的模型绑定将数据从HTTP请求映射到操作方法参数.参数既可以是简单类型,也可以是复杂类型.MVC 通过抽象绑定解决了这个问题. 2.使用模型绑定 当 ...

  3. ASP.NET Core MVC/WebAPi 模型绑定探索 转载https://www.cnblogs.com/CreateMyself/p/6246977.html

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  4. 【转】ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  5. ASP.NET Core MVC/WebAPi 模型绑定

    public class Person { public string Name { get; set; } public string Address { get; set; } public in ...

  6. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  7. ASP.NET Core MVC中URL和数据模型的匹配

    Http GET方法 首先我们来看看GET方法的Http请求,URL参数和ASP.NET Core MVC中Controller的Action方法参数匹配情况. 我定义一个UserController ...

  8. ASP.NET Core MVC如何上传文件及处理大文件上传

    用文件模型绑定接口:IFormFile (小文件上传) 当你使用IFormFile接口来上传文件的时候,一定要注意,IFormFile会将一个Http请求中的所有文件都读取到服务器内存后,才会触发AS ...

  9. ASP.NET Core MVC/WebAPi如何构建路由?

    前言 本节我们来讲讲ASP.NET Core中的路由,在讲路由之前我们首先回顾下之前所讲在ASP.NET Core中的模型绑定这其中有一个问题是我在项目当中遇见的,我们下面首先来看看这个问题. 回顾A ...

  10. 008.Adding a model to an ASP.NET Core MVC app --【在 asp.net core mvc 中添加一个model (模型)】

    Adding a model to an ASP.NET Core MVC app在 asp.net core mvc 中添加一个model (模型)2017-3-30 8 分钟阅读时长 本文内容1. ...

随机推荐

  1. [转帖]Linux中的Page cache和Buffer cache详解

    1.内存情况 在讲解Linux内存管理时已经提到,当你在Linux下频繁存取文件后,即使系统上没有运行许多程序,也会占用大量的物理内存.这是因为当你读写文件的时候,Linux内核为了提高读写的性能和速 ...

  2. linux中如何统计千万个文件总和

    很简单.很简单.很简单.重要事情说三遍 命令:ls | grep '匹配信息' | wc -l ls查看该目录下的所有文件,果然隐藏文件也要匹配上的话,需要:ls -a grep匹配,如查看文件中有. ...

  3. C++ 实现的Ping类的封装

    Ping 使用 Internet 控制消息协议(ICMP)来测试主机之间的连接.当用户发送一个 ping 请求时,则对应的发送一个 ICMP Echo 请求消息到目标主机,并等待目标主机回复一个 IC ...

  4. Intel(R) Ethernet Controller X710驱动升级

    环境 CentOS Linux release 7.9.2009 (Core) 升级 先查看原驱动版本 [root@xcdcs ~]# lspci |grep net 01:00.0 Ethernet ...

  5. VB6的WindowsXP控件引擎 - 开源研究系列文章

    这几天翻了一下原来VB6的代码,将一些有用的代码进行了整理,然后将这些代码记录下来,开源出来,让需要的朋友能够进行代码复用. 这次介绍的是一个VB6的WindowXP的控件引擎代码,主要是在程序启动的 ...

  6. 手撕Udp套接字|实现群聊通信|实现Windows & Linux通信交互

    ​ 专栏和Git地址 操作系统https://blog.csdn.net/yu_cblog/category_12165502.html?spm=1001.2014.3001.5482UdpSocke ...

  7. 小知识:RMAN备份当前控制文件报错ORA-245

    在一个备份的case上遇到备份控制文件报错ORA-245,最终通过修改snapshot controlfile默认位置到ASM磁盘组后解决. 1.问题复现 回来后就想快速记录下这个小知识点,打开尘封的 ...

  8. .NET 云原生架构师训练营(模块二 基础巩固 MVC终结点)--学习笔记

    2.3.4 Web API -- MVC终结点 MVC与MVVM 模型绑定 自定义模型绑定器 模型验证 返回数据处理 MVC与MVVM MVC ASP.NET Core MVC 概述:https:// ...

  9. CF1913

    C 先用桶记录各个位数量. 对于每次询问,从低位到高位扫,先用掉一个当前位的,然后把当前位的全部转化成 \(cnt/2\) 个更高位的. D 有一个思路:其实删区间可以视作删相邻两个数.(然鹅这题不是 ...

  10. 鹏程杯子2023 pwn

    主要就是修改stdin的最后几位,使他变为write,然后泄露libc,为所欲为即可. 本人是卡在不知道stdin那里可以修改. 然后使用一下jmp qword rbp这个gadget 0x400a9 ...