《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.taodudu.cc/news/show-911519.html

相关文章:

  • .NET Core开发实战(第34课:MediatR:轻松实现命令查询职责分离模式(CQRS))--学习笔记(上)...
  • 远程终端管理和检测系统
  • 《ASP.NET Core 3 框架揭秘(上下册)》送书结果公告
  • MySQL对JSON类型UTF-8编码导致中文乱码探讨
  • .NET Core开发实战(第34课:MediatR:轻松实现命令查询职责分离模式(CQRS))--学习笔记(下)...
  • Asp.Net Core 中IdentityServer4 实战之角色授权详解
  • [ASP.NET Core 3.1]浏览器嗅探解决部分浏览器丢失Cookie问
  • Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端
  • Asp.Net Core Ocelot Consul 微服务
  • .Neter们,你真的应该了解下EFCore3.x
  • istio回归「单体应用」对我们的启发
  • ASP.NET MVC升级到ASP.NET Core MVC踩坑小结
  • 你所不知道的 C# 中的细节
  • CIO/CTO都应该掌握和了解的EA(企业架构)
  • 扛并发主力军,引入应用层缓存
  • 使用 VMware + win10 + vs2019 从零搭建双机内核调试环境
  • C#中的9个“黑魔法”与“骚操作”
  • .NET Core开发实战(第35课:MediatR:让领域事件处理更加优雅)--学习笔记
  • 解析“60k”大佬的19道C#面试题(下)
  • DotNetCore三大Redis客户端对比和使用心得
  • (译)创建.NET Core多租户应用程序-租户解析
  • 【要闻】Kubernetes无用论诞生、Elasticsearch 7.6.2 发布
  • 玩转控件:对Dev中GridControl控件的封装和扩展
  • Docker-HealthCheck指令探测ASP.NET Core容器健康状态
  • ASP.NET Core分布式项目实战(课程介绍,MVP,瀑布与敏捷)--学习笔记
  • 使用EF.Core将同一模型映射到多个表
  • EntityFramework Core 3.x添加查询提示(NOLOCK)
  • Xamarin.Forms客户端第一版
  • 给 EF Core 查询增加 With NoLock
  • ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...

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

  1. 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]

    <200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...

  2. ASP.NET Core 入门教程 2、使用ASP.NET Core MVC框架构建Web应用

    原文:ASP.NET Core 入门教程 2.使用ASP.NET Core MVC框架构建Web应用 一.前言 1.本文主要内容 使用dotnet cli创建基于解决方案(sln+csproj)的项目 ...

  3. [ASP.NET Core 3框架揭秘] 异步线程无法使用IServiceProvider?

    标题反映的是上周五一个同事咨询我的问题,我觉得这是一个很好的问题.这个问题有助于我们深入理解依赖注入框架在ASP.NET Core中的应用,以及服务实例的生命周期. 一.问题重现 我们通过一个简单的实 ...

  4. 这本694页的程序员砖头书让你精通ASP.NET Core MVC

    ASP.NET Core MVC是一个来自微软的Web应用程序开发框架,它结合了模型-视图-控制器(MVC)体系结构的有效性和整洁性.敏捷开发的想法和技术,以及.NET平台的最佳部分. 1.1 ASP ...

  5. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  6. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

  7. 如何在 Asp.Net Core MVC 中处理 null 值

    译文链接:https://www.infoworld.com/article/3434624/how-to-handle-null-values-in-aspnet-core-mvc.html 传统的 ...

  8. ASP.NET CORE MVC 实现减号分隔(Kebab case)样式的 URL

    ASP.NET CORE MVC 中,默认的 Route 模板是: /{controller}/{action}  .我们可以通过开启 URL 小写转换将 URL 变为小写,但此方式在 Control ...

  9. 《Pro ASP.NET Core MVC 2, 7th Edition》翻译计划及章节目录

    说明 <Pro ASP.NET Core MVC 2, 7th Edition>主要介绍了ASP.NET Core MVC框架web应用程序的开发. 之前有翻译了.NET Core MVC ...

最新文章

  1. 如何学习修改linux系统固件,基于Linux的固件,如何实现更新的好方法?
  2. Ajax异步加载的知识点
  3. 经典C语言程序100例之三五
  4. Microsoft Dynamics CRM 2015 新增功能 介绍 高级查找功能
  5. FreeMarker中文API手冊(完整)
  6. 【LeetCode 剑指offer刷题】字符串题12:Valid Palindrome(回文词系列)
  7. init.x java_详谈jvm--Java中init和clinit的区别
  8. php 抽象 接口类 区别,PHP 抽象類和接口區別
  9. 数据库mysql的注释怎么加_mysql表如何添加字段注释
  10. mysql基础之日志管理(查询日志、慢查询日志、错误日志、二进制日志、中继日志、事务日志)...
  11. MPLS OPTION-B
  12. Android万能布局检查器UI Automator Viewer使用教程、环境配置和Mac无法打开问题解决(uiautomatorviewer,android studio,layer,查看,错误)
  13. 移动硬盘拒绝访问怎么修复?
  14. 职场生涯的3个步骤--法、儒、道家的管理哲学
  15. 职场故事让你如梦初醒
  16. 外地驾照迁入北京流程
  17. toLocaleString也太好用了吧!(超方便转千分位,中文数字等)
  18. 什么是RFID技术?
  19. ensp使用web登录防火墙
  20. 宗镜录略讲——南怀瑾老师——系列11

热门文章

  1. Ext4.2文件目录及页面默认导入文件
  2. [导入]体验Asp.Net Mvc Preview5(3)-探索ModelBinder的工作原理
  3. mysql 1005 - can't create table_关于创建数据表报错一例(ERROR 1005 Can’t create table (errno: 121))...
  4. TCP/IP:IP多播选路
  5. mt19937 -- 高质量随机数
  6. Linux内核驱动GPIO的使用
  7. [C++]VS2005(VC8) 使用 Boost
  8. log4net日志插件的使用
  9. CSS Id 和 Class
  10. SharpDeveloeper开发ASP.NET MVC汗流浃背