通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]:请求的响应
2021-04-02 20:25
YPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
标签:扩展方法 getattr 类型 应用程序 des 实例 扫描 odi desc
《200行代码,7个对象——让你了解ASP.NET Core框架的本质》让很多读者对ASP.NET Core管道有了真实的了解。在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分析一下MVC框架的设计与实现原理,希望这篇文章能够满足你们的需求。在对本章内容展开介绍之前,顺便作一下广告:《ASP.NET Core 3框架揭秘》已经开始销售,限时5折优惠还有最后3天,有兴趣可以扫描右边二维码或者从这里入群购买。
我们在《[上篇]:路由整合》将定义在Controller类型中的Action方法简化成只返回Task或者Void的方法,并让方法自身去完成包括对请求予以相应的所有请求处理任务,但真实的MVC框架并非如此。真正的MVC框架中具有一个名为IActionResult的重要结构,顾名思义,IActionResult对象一般会作为Action方法的返回值,针对请求的响应任务基本上会由这个对象来实现。
源代码下载:
IActionResult的执行
IActionResult的类型转换
一、IActionResult
作为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; }
二、执行IActionResult对象
接下来我们将Action方法返回类型的约束放宽,除了Task和Void,Action方法的返回类型还可以是IActionResult、Task
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 TaskToActionResultAsync(object result) { if (result == null) { return NullActionResult.Instance; } if (result is Task taskOfActionResult) { return await taskOfActionResult; } if (result is ValueTask 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
public class FoobarController : Controller { private static readonly string _html = @"Hello Hello World!
"; [HttpGet("/{foo}")] public TaskFooAsync() { return Task.FromResult (new ContentResult(_html, "text/html")); } public IActionResult Bar() => new ContentResult(_html, "text/plain"); }
演示程序启动之后,如果采用与前面一样的URL访问定义在FoobarController的两个Action方法,我们会在浏览器上得到如下图所示的输出结果。由于FooAsync方法将内容类型设置为 “text/html” ,所以浏览器会将返回的内容作为一个HTML文档进行解析,但是Bar方法将内容类型设置为 “text/plain” ,所以返回的内容会原封不动地输出到浏览器上。源代码从这里下载。
三、IActionResult类型转化
前面的内容对Task方法的返回类型做出了一系列的约束,但是我们知道在真正的MVC框架中,定义在Controller中的Action方法可以采用任意的类型。为了解决这个问题,我们可以考虑Action方法返回的数据对象转换成一个IActionResult对象。我们将类型转换规则定义成通过IActionResultTypeMapper接口表示的服务,针对IActionResult的类型转换体现在Convert方法上。值得一提的是,Convert方法表示待转换的对象的value参数并不一定是Action方法的返回值,而是具体数据对象。如果Action方法的返回值是一个Task
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
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 TaskConvertFromTaskAsync (Task returnValue, IActionResultTypeMapper mapper) { var result = await returnValue; return result is IActionResult actionResult ? actionResult : mapper.Convert(result, typeof(TValue)); } private static async Task ConvertFromValueTaskAsync ( ValueTask returnValue, IActionResultTypeMapper mapper) { var result = await returnValue; return result is IActionResult actionResult ? actionResult : mapper.Convert(result, typeof(TValue)); } … }
如下所示的是InvokeAsync方法针对Action的执行。在执行了目标Action方法并得到原始的返回值后,我们调用了ToActionResultAsync方法将返回值转换成Task
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(); var actionResult = await ToActionResultAsync( returnValue, actionMethod.ReturnType, mapper); await actionResult.ExecuteResultAsync(ActionContext); } private Task ToActionResultAsync(object returnValue, Type returnType, IActionResultTypeMapper mapper) { //Null if (returnValue == null || returnType == typeof(Task) || returnType == typeof(ValueTask)) { return Task.FromResult (NullActionResult.Instance); } //IActionResult if (returnValue is IActionResult actionResult) { return Task.FromResult(actionResult); } //Task 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 )taskOfResult; } //ValueTask 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 )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() .AddSingleton () .AddSingleton () .AddSingleton () .AddSingleton (); } }
为了验证模拟框架对Action方法的任意返回类型的支持,我们将前面演示实例定义的FoobarController做了如下的修改。如代码片段所示,我们在FoobarController类型中定义了四个Action方法,它们返回的类型分别为Task
public class FoobarController : Controller { private static readonly string _html = @"Hello Hello World!
"; [HttpGet("/foo")] public TaskFooAsync() => Task.FromResult(new ContentResult(_html, "text/html")); [HttpGet("/bar")] public ValueTask BarAsync() => new ValueTask (new ContentResult(_html, "text/html")); [HttpGet("/baz")] public Taskstring> BazAsync() => Task.FromResult(_html); [HttpGet("/qux")] public ValueTaskstring> QuxAsync() => new ValueTaskstring>(_html); }
我们在上述四个Action方法上通过标注HttpGetAttribute特性将路由模板分别设置为“/foo”、“/bar”、“/baz”和“/qux”,所以我们可以采用相应的URL来访问这四个Action方法。下图所示的是这个Action的响应内容在浏览器上的呈现。由于Action方法Baz和Qux返回的是一个字符串,按照ActionResultTypeMapper类型提供的转换规则,最终返回的将是以此字符串作为响应内容,内容类型为 “text/plain” 的ContentResult对象。源代码从这里下载。
通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]:路由整合
通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]: 请求响应
通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[下篇]:参数绑定
通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]:请求的响应
标签:扩展方法 getattr 类型 应用程序 des 实例 扫描 odi desc
原文地址:https://www.cnblogs.com/artech/p/12550074.html
文章标题:通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]:请求的响应
文章链接:http://soscw.com/index.php/essay/71527.html