通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]:请求响应

《200行代码,7个对象——让你了解ASP.NET Core框架的本质》让很多读者对ASP.NET Core管道有了真实的了解。在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分析一下MVC框架的设计与实现原理,希望这篇文章能够满足你们的需求,源代码可以通过原文下载。

01

IActionResult

我们在前面将定义在Controller类型中的Action方法简化成只返回Task或者Void的方法,并让方法自身去完成包括对请求予以相应的所有请求处理任务,但真实的MVC框架并非如此。真正的MVC框架中具有一个名为IActionResult的重要结构,顾名思义,IActionResult对象一般会作为Action方法的返回值,针对请求的响应任务基本上会由这个对象来实现。

作为Action方法执行结果旨在对请求做最终响应的IActionResult接口同样具有极为简单的定义。如下main的代码片段所示,IActionResult对象针对请求的响应实现在它唯一的ExecuteResultAsync方法中,针对待执行Action的ActionContext上下文是其唯一的输入参数。

public interface IActionResult
{Task ExecuteResultAsync(ActionContext context);
}

针对不同的请求响应需求,MVC框架为我们定义了一系列的IActionResult实现类型,应用程序同样也可以根据需要定义自己的IActionResult类型。作为演示,我们定义了如下这个ContentResult类型,它将指定的字符串作为响应主体的内容,具体的内容类型(媒体内容或者MIME类型)则可以灵活指定。

public class ContentResult : IActionResult
{private readonly string _content;private readonly string _contentType;public ContentResult(string content, string contentType){_content     = content;_contentType     = contentType;}public Task ExecuteResultAsync(ActionContext context){var response = context.HttpContext.Response;response.ContentType = _contentType;return response.WriteAsync(_content);}
}

由于Action方法可能没有返回值,为了使Action执行流程(执行Action方法=>将返回值转化成IActionResult对象=>执行IActionResult对象)显得明确而清晰,我们定义了如下这个“什么都没做”的NullActionResult类型,它利用静态只读属性Instance返回一个单例的NullActionResult对象。

public sealed class NullActionResult : IActionResult
{private NullActionResult() { }public static NullActionResult Instance { get; } = new NullActionResult();public Task ExecuteResultAsync(ActionContext context) => Task.CompletedTask;
}

02

执行IActionResult对象

接下来我们将Action方法返回类型的约束放宽,除了Task和Void,Action方法的返回类型还可以是IActionResult、Task<IActionResult>和ValueTask<IActionResult>。基于这个新的约定,我们需要对前面定义的ControllerActionInvoker的InvokeAsync方法作如下的修改。如代码片段所示,在执行目标Action方法之后,我们调用ToActionResultAsync方法将返回对象转换成一个Task<IActionResult>对象,最终针对请求的响应只需要直接执行这个IActionResult对象即可。

public class ControllerActionInvoker : IActionInvoker
{public ActionContext ActionContext { get; }public ControllerActionInvoker(ActionContext actionContext) => ActionContext = actionContext;public async Task InvokeAsync(){var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;var controllerType = actionDescriptor.ControllerType;var requestServies = ActionContext.HttpContext.RequestServices;var controllerInstance = ActivatorUtilities.CreateInstance(requestServies, controllerType);if (controllerInstance is Controller controller){controller.ActionContext = ActionContext;}var actionMethod = actionDescriptor.Method;var result = actionMethod.Invoke(controllerInstance, new object[0]);var actionResult = await ToActionResultAsync(result);await actionResult.ExecuteResultAsync(ActionContext);}private async Task<IActionResult> ToActionResultAsync(object result){if (result == null){return NullActionResult.Instance;}if (result is Task<IActionResult> taskOfActionResult){return await taskOfActionResult;}if (result is ValueTask<IActionResult> valueTaskOfActionResult){return await valueTaskOfActionResult;}if (result is IActionResult actionResult){return actionResult;}if (result is Task task){await task;return NullActionResult.Instance;}throw new InvalidOperationException("Action method's return value is invalid.");}
}

我们接下来将前面定义的ContentResult引入到演示实例的FoobarController中。如下面的代码片段所示,我们将Action方法FooAsync和Bar的返回类型分别替换成Task<IActionResult>和IActionResult,具体返回的都是一个ContentResult对象。两个ContentResult对象都将同一段HTML片段作为响应的主体内容,但是FooAsync方法将内容类型设置成 “text/html” ,而Bar方法则将其设置为 “text/plain” 。

public class FoobarController : Controller
{private static readonly string _html =
@"<html>
<head><title>Hello</title>
</head>
<body><p>Hello World!</p>
</body>
</html>";[HttpGet("/{foo}")]public Task<IActionResult> FooAsync(){return Task.FromResult<IActionResult>(new ContentResult(_html, "text/html"));}public IActionResult Bar() => new ContentResult(_html, "text/plain");
}

演示程序启动之后,如果采用与前面一样的URL访问定义在FoobarController的两个Action方法,我们会在浏览器上得到如下图所示的输出结果。由于FooAsync方法将内容类型设置为 “text/html” ,所以浏览器会将返回的内容作为一个HTML文档进行解析,但是Bar方法将内容类型设置为 “text/plain” ,所以返回的内容会原封不动地输出到浏览器上。

03

IActionResult类型转化

前面的内容对Task方法的返回类型做出了一系列的约束,但是我们知道在真正的MVC框架中,定义在Controller中的Action方法可以采用任意的类型。为了解决这个问题,我们可以考虑Action方法返回的数据对象转换成一个IActionResult对象。我们将类型转换规则定义成通过IActionResultTypeMapper接口表示的服务,针对IActionResult的类型转换体现在Convert方法上。值得一提的是,Convert方法表示待转换的对象的value参数并不一定是Action方法的返回值,而是具体数据对象。如果Action方法的返回值是一个Task<TResult>或者ValueTask<TResult>对象,它们的Result属性返回的参数这个待转换的数据对象。

public interface IActionResultTypeMapper
{IActionResult Convert(object value, Type returnType);
}

简单起见,我们定义了如下这个ActionResultTypeMapper类型将作为模拟框架对IActionResultTypeMapper接口的默认实现。如代码片段所示,Convert方法将返回个内容类型为“text/plain”的ContentResult对象,原始对象字符串描述(ToString方法的返回值)将作为响应主题的内容。

public class ActionResultTypeMapper : IActionResultTypeMapper
{public IActionResult Convert(object value, Type returnType)=> new ContentResult(value.ToString(), "text/plain");
}

当我们将针对Action方法返回类型的限制去除之后,我们的ControllerActionInvoker自然需要作进一步修改。Action方法可能会返回一个Task<TResult>或者ValueTask<TResult>对象(泛型参数TResult可以是任意类型),所以我们在ControllerActionInvoker类型定义了如下两个静态方法(ConvertFromTaskAsync<TValue>和ConvertFromValueTaskAsync<TValue>)将它们转换成Task<IActionResult>对象,如果返回的不是一个IActionResult对象,作为参数的IActionResultTypeMapper对象将来进行类型转换。我们定义在两个静态只读字段(_taskConvertMethod和_valueTaskConvertMethod)来保存描述这两个泛型方法的MethodInfo对象。

public class ControllerActionInvoker : IActionInvoker
{private static readonly MethodInfo _taskConvertMethod;private static readonly MethodInfo _valueTaskConvertMethod;static ControllerActionInvoker(){var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic| BindingFlags.Static;_taskConvertMethod = typeof(ControllerActionInvoker).GetMethod(nameof(ConvertFromTaskAsync), bindingFlags);_valueTaskConvertMethod = typeof(ControllerActionInvoker).GetMethod(nameof(ConvertFromValueTaskAsync), bindingFlags);}private static async Task<IActionResult> ConvertFromTaskAsync<TValue>(Task<TValue> returnValue, IActionResultTypeMapper mapper){var result = await returnValue;return result is IActionResult actionResult? actionResult: mapper.Convert(result, typeof(TValue));}private static async Task<IActionResult> ConvertFromValueTaskAsync<TValue>(ValueTask<TValue> returnValue, IActionResultTypeMapper mapper){var result = await returnValue;return result is IActionResult actionResult? actionResult: mapper.Convert(result, typeof(TValue));}…
}

如下所示的是InvokeAsync方法针对Action的执行。在执行了目标Action方法并得到原始的返回值后,我们调用了ToActionResultAsync方法将返回值转换成Task<IActionResult>,最终通过执行IActionResult对象进而完成所有的请求处理任务。如果返回类型为Task<TResult>或者ValueTask<TResult>,我们会直接采用反射的方式调用ConvertFromTaskAsync<TValue>或者ConvertFromValueTaskAsync<TValue>方法(更好的方式是采用表达式树的方式执行类型转换方法以获得更好的性能)。

public class ControllerActionInvoker : IActionInvoker
{    public async Task InvokeAsync(){var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;var controllerType = actionDescriptor.ControllerType;var requestServies = ActionContext.HttpContext.RequestServices;var controllerInstance = ActivatorUtilities.CreateInstance(requestServies, controllerType);if (controllerInstance is Controller controller){controller.ActionContext = ActionContext;}var actionMethod = actionDescriptor.Method;var returnValue = actionMethod.Invoke(controllerInstance, new object[0]);var mapper = requestServies.GetRequiredService<IActionResultTypeMapper>();var actionResult = await ToActionResultAsync(returnValue, actionMethod.ReturnType, mapper);await actionResult.ExecuteResultAsync(ActionContext);}private Task<IActionResult> ToActionResultAsync(object returnValue, Type returnType, IActionResultTypeMapper mapper){//Nullif (returnValue == null || returnType == typeof(Task) || returnType == typeof(ValueTask)){return Task.FromResult< IActionResult > (NullActionResult.Instance);}//IActionResultif (returnValue is IActionResult actionResult){return Task.FromResult(actionResult);}//Task<TResult>if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)){var declaredType = returnType.GenericTypeArguments.Single();var taskOfResult = _taskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });return (Task<IActionResult>)taskOfResult;}//ValueTask<TResult>if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>)){var declaredType = returnType.GenericTypeArguments.Single();var valueTaskOfResult = _valueTaskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });return (Task<IActionResult>)valueTaskOfResult;}return Task.FromResult(mapper.Convert(returnValue, returnType));}
}

从上面的代码片段可以看出,在进行针对IActionResult的类型转换过程中使用到的IActionResultTypeMapper对象是从针对当前请求的依赖注入容器中提取的,所以我们在应用启动之前需要作针对性的服务注册。我们将针对IActionResultTypeMapper的服务注册添加到之前定义的AddMvcControllers扩展方法中。

public static class ServiceCollectionExtensions
{public static IServiceCollection AddMvcControllers(this IServiceCollection services){return services.AddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>().AddSingleton<IActionInvokerFactory, ActionInvokerFactory>().AddSingleton<IActionDescriptorProvider, ControllerActionDescriptorProvider>().AddSingleton<ControllerActionEndpointDataSource, ControllerActionEndpointDataSource>().AddSingleton<IActionResultTypeMapper, ActionResultTypeMapper>();}
}

为了验证模拟框架对Action方法的任意返回类型的支持,我们将前面演示实例定义的FoobarController做了如下的修改。如代码片段所示,我们在FoobarController类型中定义了四个Action方法,它们返回的类型分别为Task<ContentResult>、ValueTask<ContentResult>、Task<String>、ValueTask<String>,ContentResult对象的内容和直接返回的字符串都是一段相同的HTML。

public class FoobarController : Controller
{private static readonly string _html =
@"<html>
<head><title>Hello</title>
</head>
<body><p>Hello World!</p>
</body>
</html>";[HttpGet("/foo")]public Task<ContentResult> FooAsync()=> Task.FromResult(new ContentResult(_html, "text/html"));[HttpGet("/bar")]public ValueTask<ContentResult> BarAsync()=> new ValueTask<ContentResult>(new ContentResult(_html, "text/html"));[HttpGet("/baz")]public Task<string> BazAsync() => Task.FromResult(_html);[HttpGet("/qux")]public ValueTask<string> QuxAsync() => new ValueTask<string>(_html);
}

我们在上述四个Action方法上通过标注HttpGetAttribute特性将路由模板分别设置为“/foo”、“/bar”、“/baz”和“/qux”,所以我们可以采用相应的URL来访问这四个Action方法。下图所示的是这个Action的响应内容在浏览器上的呈现。由于Action方法Baz和Qux返回的是一个字符串,按照ActionResultTypeMapper类型提供的转换规则,最终返回的将是以此字符串作为响应内容,内容类型为 “text/plain” 的ContentResult对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux strcpy函数实现,strcpy(char *dest , char *src)的漏洞

前言&#xff1a;研究了几天DOS下的溢出原理&#xff0c;最后明白了其实原理都很简单关键是要懂得为什么C语言的strcpy函数有漏洞&#xff0c;为什么对这个函数的不正常使用会造成溢出。一节&#xff1a;介绍strcpy函数能看到这篇文章的人可能都知道问题很多是出在它的身上吧呵…

洛谷 P2853 [USACO06DEC]Cow Picnic S-dfs

输入: 2 4 4 2 3 1 2 1 4 2 3 3 4输出: 2代码如下: #include <iostream> #include <vector> #include <cstring> using namespace std; const int N 1010; bool vis[N]; vector<int>v[N]; int r[N]; int mk[N];void dfs(int u) {mk[u];for (int i …

.NET Core开发实战(第34课:MediatR:轻松实现命令查询职责分离模式(CQRS))--学习笔记(上)...

34 | MediatR&#xff1a;轻松实现命令查询职责分离模式&#xff08;CQRS&#xff09;核心对象IMeditatorIRequese、IRequestIRequestHandler<in TRequest, TResponse>源码链接&#xff1a;https://github.com/witskeeper/geektime/tree/master/samples/MediatorDemo首先…

Linux和unix发行版本,UNIX发展历史和发行版本

[TOC]>[success] # UNIX发展历史和发行版本**UNIX**跟**Linux**之间的关系&#xff0c;用**父子**比喻更为恰当&#xff0c;因为学习了**Linux**以后更有助于以后学习**UNIX**&#xff0c;无论是**Linux**还是**UNIX**的**内核**都是用**C语言**写的&#xff0c;早起是用**汇…

十一届蓝桥杯国赛 玩具蛇-dfs

【问题描述】 小蓝有一条玩具蛇&#xff0c;一共有 16 节&#xff0c;上面标着数字 1 至 16。每一节都是一 个正方形的形状。相邻的两节可以成直线或者成 90 度角。 小蓝还有一个 4 4 的方格盒子&#xff0c;用于存放玩具蛇&#xff0c;盒子的方格上依次标着 字母 A 到 P 共 1…

远程终端管理和检测系统

TerminalMACS(Terminal Manager And Check System)远程终端管理和检测系统本文同步更新地址&#xff1a;https://dotnet9.com/11429.html一、本系统可监控多种终端资源&#xff1a;移动端AndroidiOSPC端WindowsLinuxMac二、整个系统分为三类进程&#xff1a;被控端(Client)被控…

《ASP.NET Core 3 框架揭秘(上下册)》送书结果公告

【免费送书】.Net5实操后的我一夜未眠&#xff0c;来个大胆预测&#xff01;的送书抽奖结果已经出来了&#xff1a;这位中奖的同学尽快填写收货地址&#xff0c;4/2 日还没有完成填写将作废&#xff0c;奖品可是热门的《ASP.NET Core 3 框架揭秘&#xff08;上下册&#xff09;…

十一届蓝桥杯国赛 扩散-多源bfs

【问题描述】 小蓝在一张无限大的特殊画布上作画。 这张画布可以看成一个方格图&#xff0c;每个格子可以用一个二维的整数坐标表示。 小蓝在画布上首先点了一下几个点&#xff1a;(0, 0), (2020, 11), (11, 14), (2000, 2000)。 只有这几个格子上有黑色&#xff0c;其它位置都…

linux下Qt编写串口调试助手,如何在linux下用QT写一个简单的串口调试助手

如何在linux下用QT写一个简单的串口调试助手QT5串口类在QT5以前&#xff0c;编写串口一般使用的是qextserialport类&#xff0c;但在QT5之后有了QT自带的串口类SerialPort(串口基础类)和SerialPortInfo(串口信息类)使用方法pro中添加QT serialport工程中包含相应的头文件#incl…

当代年轻人到底怎么跨越阶层?

0最近大道理讲的有点多&#xff0c;鸡汤灌多了容易腻味&#xff0c;还容易上火。别说你们烦我&#xff0c;我自己讲的也烦&#xff0c;感觉像一个叨逼叨的老头&#xff0c;天天灌一些被90后、00后唾弃的东西。毕竟天天熬鸡汤不仅累还容易熏晕自己。很多东西吧&#xff0c;的确是…

十一届蓝桥杯国赛 美丽的2-枚举

【问题描述】 小蓝特别喜欢 2&#xff0c;今年是公元 2020 年&#xff0c;他特别高兴。 他很好奇&#xff0c;在公元 1 年到公元 2020 年&#xff08;包含&#xff09;中&#xff0c;有多少个年份的数位中 包含数字 2&#xff1f; 代码如下: #include <iostream> using…

分析linux相关日志文件,Linux日志系统与分析.pdf

Linux日志系统与分析1目录 Linux日志系统简介 Linux日志分析 案例介绍&#xff1a; Linux日志入侵发现Linux日志系统简介 日志的主要用途是系统审计、监测追踪和分析统计。 为了保证 Linux 正常运行、准确定位系统问题&#xff0c;认真检查日志文件是管理员的一项非常…

MySQL对JSON类型UTF-8编码导致中文乱码探讨

继上文发表之后&#xff0c;结合评论意见并亲自验证最终发现是编码的问题&#xff0c;但是对于字符编码还是有点不解&#xff0c;于是乎&#xff0c;有了本文&#xff0c;我们来学习字符编码&#xff0c;在学习的过程中&#xff0c;我发现对于MySQL中JSON类型的编码导致数据中文…

Linux 文本格式显示折线图,linux 折线图

可以这样回答&#xff1a;emmm 怎么说呢。数据库就是用来存储数据的仓库&#xff0c;就像我们生活中存放物品的容器一样&#xff0c;但是容器也有通用和专用之分&#xff0c;比如塑料袋就是通用容器&#xff0c;因为塑料袋可以装各种东西&#xff1a;即可以装糖果&#xff0c;也…

十一届蓝桥杯国赛 本质上升序列-dp

【问题描述】 小蓝特别喜欢单调递增的事物。 在一个字符串中&#xff0c;如果取出若干个字符&#xff0c;将这些字符按照在字符串中的顺 序排列后是单调递增的&#xff0c;则成为这个字符串中的一个单调递增子序列。 例如&#xff0c;在字符串 lanqiao 中&#xff0c;如果取出字…

互联网公司的大龄社畜

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一某年&#xff0c;我有幸加入了一家互联网公司B公司。互联网公司无处不充满了奋斗的精神。以此为背景。二有一天&#xff0c;有同事跟我说&#xff1a;你发现没&#xff0c…

LeetCode 1122 数组的相对排序-简单-unordered_map容器的应用

给你两个数组&#xff0c;arr1 和 arr2&#xff0c; arr2 中的元素各不相同 arr2 中的每个元素都出现在 arr1 中对 arr1 中的元素进行排序&#xff0c;使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。 示例&#x…

linux系统shell知识点,linux shell 知识点

1.if语句语法为&#xff1a;if [ 条件表达式 ]; then.....fi条件表达式类型&#xff1a;文件表达式if [ -f file ] 如果文件存在 if [ -d ... ] 如果目录存在 if [ -s file ] 如果文件存在且非空 if [ -r file]如果文件存在且可读 if [ -w file]如果文件存在且可写 if [ -x fil…

.NET Core开发实战(第34课:MediatR:轻松实现命令查询职责分离模式(CQRS))--学习笔记(下)...

34 | MediatR&#xff1a;轻松实现命令查询职责分离模式&#xff08;CQRS&#xff09;实际上我们在定义我的查询的时候&#xff0c;也可以这样定义&#xff0c;例如我们定义一个 MyOrderQuery&#xff0c;把订单的所有名称都输出出去namespace GeekTime.API.Application.Querie…

linux连接http报301解决,https下不加www的301强制跳转

不少浏览器都开始逐渐更新至只支持https的网站&#xff0c;所以很多http网站都需要添加对https的支持&#xff0c;这时就需要涉及到www和不加www的跳转问题&#xff0c;由于www和不加www使用的是不同的证书&#xff0c;所以需要做301跳转处理&#xff0c;方案如下&#xff1a;此…