(给DotNet加星标,提升.Net技能)
转自: FlyLolo cnblogs.com/FlyLolo/p/11503207.html
本章讲一下它的"逆过程",如何将请求结果按照客户端想要的格式返回去。
一、常见的返回类型
以系统模板默认生成的Home/Index这个Action来说,为什么当请求它的时候会返回一个Html页面呢?除了这之外,还有JSON、文本等类型,系统是如何处理这些不同的类型的呢?
首先来说几种常见的返回类型的例子,并用Fiddler请求这几个例子看一下结果,涉及到的一个名为Book的类,代码为:
public class Book
{
public string Code { get; set; }
public string Name { get; set; }
}
1、ViewResult类型
public class HomeController : Controller
{
public IActionResult Index(){
return View();
}
}
返回一个Html页面,Content-Type值为:text/html; charset=utf-8
2、string类型
public string GetString(){
return "Hello Core";
}
返回字符串“Hello Core”,Content-Type值为:text/plain; charset=utf-8
3、JSON类型
public JsonResult GetJson(){
return new JsonResult(new Book() { Code = "1001", Name = "ASP" });
}
返回JSON,值为:
{"code":"1001","name":"ASP"}
Content-Type值为:Content-Type: application/json; charset=utf-8
4、直接返回实体类型
public Book GetModel(){
return new Book() { Code = "1001", Name = "ASP" };
}
同样是返回JSON,值为:
{"code":"1001","name":"ASP"}
Content-Type值同样是:Content-Type: application/json; charset=utf-8
5、void类型
public void GetVoid(){}
没有返回结果也没有Content-Type值。
下面看几个异步方法:
6、Task类型
public async Task GetTaskNoResult(){
await Task.Run(() => { });
}
与void类型一样,没有返回结果也没有Content-Type值。
7、Task类型
public async Task<string> GetTaskString(){
string rtn = string.Empty;
await Task.Run(() => { rtn = "Hello Core"; });
return rtn;
}
与string类型一样,返回字符串“Hello Core”,Content-Type值为:text/plain; charset=utf-8
8、Task类型
public async TaskGetTaskJson(){
JsonResult jsonResult = null;
await Task.Run(() => { jsonResult = new JsonResult(new Book() { Code = "1001", Name = "ASP" }); });
return jsonResult;
}
与JSON类型一样,返回JSON,值为:
[{"code":"1001","name":"ASP"},{"code":"1002","name":"Net Core"}]
Content-Type值为:Content-Type: application/json; charset=utf-8
还有其他类型,这里暂不列举了,总结一下:
1、返回结果有空、Html页面、普通字符串、JSON字符串几种。
2、对应的Content-Type类型有空、text/html、text/plain、application/json几种。
3、异步Action的返回结果,和其对应的同步Action返回结果类型一致。
下一节我们看一下系统是如何处理这些不同的类型的。
二、内部处理机制解析
1、总体流程
通过下图 来看一下总体的流程:
这涉及三部分内容:
第一部分,在invoker的生成阶段。在第14章讲invoker的生成的时候,讲到了Action的执行者的获取,它是从一系列系统定义的XXXResultExecutor中筛选出来的,虽然它们名为XXXResultExecutor,但它们都是Action的执行者而不是ActionResult的执行者,都是ActionMethodExecutor的子类。以Action是同步还是异步以及Action的返回值类型为筛选条件,具体这部分内容见图 14‑2所示XXXResultExecutor列表及其后面的筛选逻辑部分。
在图 17‑1中,筛选出了被请求的Action对应的XXXResultExecutor,若以Home/Index这个默认的Action为例,这个XXXResultExecutor应该是SyncActionResultExecutor。
第二部分,在Action Filters的处理阶段。这部分内容见16.5 Filter的执行,此处恰好以Action Filter为例讲了Action Filter的执行方式及Action被执行的过程。在这个阶段,会调用上文筛选出的SyncActionResultExecutor的Execute方法来执行Home/Index这个 Action。执行结果返回一个IActionResult。
第三部分,在Result Filters的处理阶段。这个阶段和Action Filters的逻辑相似,只不过前者的核心是Action的执行,后者的核心是Action的执行结果的执行。二者都分为OnExecuting和OnExecuted两个方法,这两个方法也都在其对应的核心执行方法前后执行。
整体流程是这样,下面看一下细节。
2、ActionMethodExecutor的选择与执行
第一部分,系统为什么要定义这么多种XXXResultExecutor并且在请求的时候一个个筛选合适的XXXResultExecutor呢?从筛选规则是以Action的同步、异步以及Action的返回值类型来看,这么多种XXXResultExecutor就是为了处理不同的Action类型。
依然以Home/Index为例,在筛选XXXResultExecutor的时候,最终返回结果是SyncActionResultExecutor。
它的代码如下:
private class SyncActionResultExecutor : ActionMethodExecutor
{
public override ValueTaskExecute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,object controller,object[] arguments){
var actionResult = (IActionResult)executor.Execute(controller, arguments);
EnsureActionResultNotNull(executor, actionResult);
return new ValueTask(actionResult);
}protected override bool CanExecute(ObjectMethodExecutor executor)=> !executor.IsMethodAsync && typeof(IActionResult).IsAssignableFrom(executor.MethodReturnType);
}
XXXResultExecutor的CanExecute方法是筛选的条件,通过这个方法判断它是否适合当前请求的目标Action。它要求这个Action不是异步的并且返回结果类型是派生自IActionResult的。而Home/Index这个Action标识的返回结果是IActionResult,实际是通过View()这个方法返回的,这个方法的返回结果类型实际是IActionResult的派生类ViewResult。这样的派生类还有常见的JsonResult和ContentResult等,他们都继承了ActionResult,而ActionResult实现了IActionResult接口。所以如果一个Action是同步的并且返回结果是JsonResult或ContentResult的时候,对应的XXXResultExecutor也是SyncActionResultExecutor。
第二部分中,Action的执行是在XXXResultExecutor的Execute方法,它会进一步调用了ObjectMethodExecutor的Execute方法。
实际上所有的Action的都是由ObjectMethodExecutor的Execute方法来执行执的。
而众多的XXXResultExecutor方法的作用是调用这个方法并且对返回结果进行验证和处理。例如SyncActionResultExecutor会通过EnsureActionResultNotNull方法确保返回的结果不能为空。
如果是sting类型呢?它对应的是SyncObjectResultExecutor,代码如下:
private class SyncObjectResultExecutor : ActionMethodExecutor
{
public override ValueTaskExecute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,object controller,object[] arguments){
// Sync method returning arbitrary object
var returnValue = executor.Execute(controller, arguments);
var actionResult = ConvertToActionResult(mapper, returnValue, executor.MethodReturnType);
return new ValueTask(actionResult);
}// Catch-all for sync methodsprotected override bool CanExecute(ObjectMethodExecutor executor) => !executor.IsMethodAsync;
}
由于string不是IActionResult的子类,所以会通过ConvertToActionResult方法对返回结果returnValue进行处理。
private IActionResult ConvertToActionResult(IActionResultTypeMapper mapper, object returnValue, Type declaredType){
var result = (returnValue as IActionResult) ?? mapper.Convert(returnValue, declaredType);
if (result == null)
{
throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredType));
}
return result;
}
如果returnValue是IActionResult的子类,则返回returnValue,否则调用一个Convert方法将returnValue转换一下:
public IActionResult Convert(object value, Type returnType){
if (returnType == null)
{
throw new ArgumentNullException(nameof(returnType));
}
if (value is IConvertToActionResult converter)
{
return converter.Convert();
}
return new ObjectResult(value)
{
DeclaredType = returnType,
};
}
这个方法会判断returnValue是否实现了IConvertToActionResult接口,如果是则调用该接口的Convert方法转换成IActionResult类型,否则会将returnValue封装成ObjectResult。ObjectResult也是ActionResult的子类。下文有个ActionResult 类型就是这样,该例会介绍。
所以,针对不同类型的Action,系统设置了多种XXXResultExecutor来处理,最终结果无论是什么,都会被转换成IActionResult类型。方便在图 17‑1所示的第三部分进行IActionResult的执行。
上一节列出了多种不同的Action,它们的处理在这里就不一一讲解了。通过下图 17‑2看一下它们的处理结果:
这里有void类型没有讲到,它本身没有返回结果,但它会被赋予一个结果EmptyResult,它也是ActionResult的子类。
图 2被两行虚线分隔为三行,第一行基本都介绍过了,第二行是第一行对应的异步方法,上一节介绍常见的返回类的时候说过,这些异步方法的返回结果和对应的同步方法是一样的。不过通过图 2可知,处理它们的XXXResultExecutor方法是不一样的。
第三行的ActionResult 类型是在ASP.NET Core 2.1 引入的,它支持IActionResult的子类也支持类似string和Book这样的特定类型。
public sealed class ActionResult : IConvertToActionResult
{public ActionResult(TValue value){if (typeof(IActionResult).IsAssignableFrom(typeof(TValue)))
{
var error = Resources.FormatInvalidTypeTForActionResultOfT(typeof(TValue), "ActionResult");throw new ArgumentException(error);
}
Value = value;
}public ActionResult(ActionResult result){if (typeof(IActionResult).IsAssignableFrom(typeof(TValue)))
{
var error = Resources.FormatInvalidTypeTForActionResultOfT(typeof(TValue), "ActionResult");throw new ArgumentException(error);
}
Result = result ?? throw new ArgumentNullException(nameof(result));
}/// /// Gets the ./// public ActionResult Result { get; }/// /// Gets the value./// public TValue Value { get; }public static implicit operator ActionResult(TValue value)
{return new ActionResult(value);
}public static implicit operator ActionResult(ActionResult result)
{return new ActionResult(result);
}
IActionResult IConvertToActionResult.Convert()
{return Result ?? new ObjectResult(Value)
{
DeclaredType = typeof(TValue),
};
}
}
TValue不支持IActionResult及其子类。它的值若是IActionResult子类,会被赋值给Result属性,否则会赋值给Value属性。它实现了IConvertToActionResult接口,想到刚讲解string类型被处理的时候用到的Convert方法。当返回结果实现了IConvertToActionResult这个接口的时候,就会调用它的Convert方法进行转换。它的Convert方法就是先判断它的值是否是IActionResult的子类,如果是则返回该值,否则将该值转换为ObjectResult后返回。
所以图 2中ActionResult 类型返回的结果被加上引号的意思就是结果类型可能是直接返回的IActionResult的子类,也有可能是string和Book这样的特定类型被封装后的ObjectResult类型。
3、Result Filter的执行
结果被统一处理为IActionResult后,进入图 1所示的第三部分。这部分的主要内容有两个,分别是Result Filters的执行和IActionResult的执行。Result Filter也有OnResultExecuting和OnResultExecuted两个方法,分别在IActionResult执行的前后执行。
自定义一个MyResultFilterAttribute,代码如下:
public class MyResultFilterAttribute : Attribute, IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context) {
Debug.WriteLine("HomeController=======>OnResultExecuted");
}
public void OnResultExecuting(ResultExecutingContext context){ Debug.WriteLine("HomeController=======>OnResultExecuting");
}
}
将它注册到第一节JSON的例子中:
[MyResultFilter]
public JsonResult GetJson(){
return new JsonResult(new Book() { Code = "1001", Name = "ASP" });
}
可以看到这样的输出结果:
HomeController=======>OnResultExecuting
……Executing JsonResult……
HomeController=======>OnResultExecuted
在OnResultExecuting中可以通过设置context.Cancel = true;取消后面的工作的执行。
public void OnResultExecuting(ResultExecutingContext context){
//用于验证的代码略
context.Cancel = true;
Debug.WriteLine("HomeController=======>OnResultExecuting");
}
再看输出结果就至于的输出了
HomeController=======>OnResultExecuting
同时返回结果也不再是JSON值了,返回结果以及Content-Type全部为空。所以这样设置后,IActionResult以及OnResultExecuted都不再被执行。
在这里除了可以做一些IActionResult执行之前的验证,还可以对HttpContext.Response做一些简单的操作,例如添加个Header值:
public void OnResultExecuting(ResultExecutingContext context){
context.HttpContext.Response.Headers.Add("version", "1.2");
Debug.WriteLine("HomeController=======>OnResultExecuting");
}
除了正常返回JSON结果外,Header中会出现新添加的version
Content-Type: application/json; charset=utf-8
version: 1.2
再看一下OnResultExecuted方法,它是在IActionResult执行之后执行。因为在这个方法执行的时候,请求结果已经发送给请求的客户端了,所以在这里可以做一些日志类的操作。举个例子,假如在这个方法中产生了异常:
public void OnResultExecuted(ResultExecutedContext context){
throw new Exception("exception");
Debug.WriteLine("HomeController=======>OnResultExecuted");
}
请求结果依然会返回正常的JSON,但从输出结果中看到是不一样的
HomeController=======>OnResultExecuting
……
System.Exception: exception
发生异常,后面的Debug输出没有 执行。但却将正确的结果返回给了客户端。
Result Filter介绍完了,看一下核心的IActionResult的执行。
4、IActionResult的执行
在ResourceInvoker的case State.ResultInside阶段会调用IActionResult的执行方法InvokeResultAsync。
该方法中参数IActionResult result会被调用ExecuteResultAsync方法执行。
protected virtual async Task InvokeResultAsync(IActionResult result){
var actionContext = _actionContext;
_diagnosticSource.BeforeActionResult(actionContext, result);
_logger.BeforeExecutingActionResult(result);
try
{
await result.ExecuteResultAsync(actionContext);
}
finally
{
_diagnosticSource.AfterActionResult(actionContext, result);
_logger.AfterExecutingActionResult(result);
}
}
然所有类型的Action的结果都被转换成了IActionResult,但它们本质上还是有区别
由图 2可知,虽的。所以这个IActionResult类型的参数result实际上可能是JsonResult、ViewResult、EmptyResult等具体类型。
下面依然以第一节JSON的例子为例来看,它返回了一个JsonResult。在这里就会调用JsonResult的ExecuteResultAsync方法,JsonResult的代码如下:
public class JsonResult : ActionResult, IStatusCodeActionResult
{
//部分代码略
public override Task ExecuteResultAsync(ActionContext context){
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var services = context.HttpContext.RequestServices;
var executor = services.GetRequiredService();return executor.ExecuteAsync(context, this);
}
}
在它的ExecuteResultAsync方法中会获取依赖注入中设置的JsonResultExecutor,由JsonResultExecutor来调用ExecuteAsync方法执行后面的工作。
JsonResultExecutor的代码如下:
public class JsonResultExecutor
{
//部分代码略
public virtual async Task ExecuteAsync(ActionContext context, JsonResult result){
//验证代码略
var response = context.HttpContext.Response;
ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
result.ContentType,
response.ContentType,
DefaultContentType,
out var resolvedContentType,
out var resolvedContentTypeEncoding);
response.ContentType = resolvedContentType;
if (result.StatusCode != null)
{
response.StatusCode = result.StatusCode.Value;
}
var serializerSettings = result.SerializerSettings ?? Options.SerializerSettings;
Logger.JsonResultExecuting(result.Value);
using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.ArrayPool = _charPool;
jsonWriter.CloseOutput = false;
jsonWriter.AutoCompleteOnClose = false;
var jsonSerializer = JsonSerializer.Create(serializerSettings);
jsonSerializer.Serialize(jsonWriter, result.Value);
}
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
// buffers. This is better than just letting dispose handle it (which would result in a synchronous write).
await writer.FlushAsync();
}
}
}
JsonResultExecutor的ExecuteAsync方法的作用就是将JsonResult中的值转换成JSON串并写入context.HttpContext.Response. Body中。至此JsonResult执行完毕。
ViewResult会有对应的ViewExecutor来执行,会通过相应的规则生成一个 Html页面。
而EmptyResult的ExecuteResult方法为空,所以不会返回任何内容。
public class EmptyResult : ActionResult
{
///
public override void ExecuteResult(ActionContext context){
}
}
5、下集预告
对于以上几种类型返回结果的格式是固定的,JsonResult就会返回JSON格式,ViewResult就会返回Html格式。
但是从第一节的例子可知,string类型会返回string类型的字符串,而Book这样的实体类型却会返回JSON。
由图 2可知这两种类型在执行完毕后,都被封装成了ObjectResult,那么ObjectResult在执行的时候又是如何被转换成string和JSON两种格式的呢?
下一章继续这个话题。
推荐阅读
(点击标题可跳转阅读)
.NET Core应用中使用分布式缓存及内存缓存
ASP.NET Core中使用Csp标头对抗Xss攻击
.NET 分布式自增Id组件
看完本文有收获?请转发分享给更多人
关注「DotNet」加星标,提升.Net技能
好文章,我在看❤️