上一章讲了系统如何将客户端提交的请求数据格式化处理成我们想要的格式并绑定到对应的参数,本章讲一下它的“逆过程”,如何将请求结果按照客户端想要的格式返回去。(ASP.NET Core 系列目录)

一、常见的返回类型

以系统模板默认生成的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 = "", Name = "ASP" });
}

返回JSON,值为:

{"code":"1001","name":"ASP"}

Content-Type值为:Content-Type: application/json; charset=utf-8

4.直接返回实体类型

public Book GetModel()
{
return new Book() { Code = "", 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<string>类型

    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<JsonResult>类型

    public async Task<JsonResult> GetTaskJson()
{
JsonResult jsonResult = null;
await Task.Run(() => { jsonResult = new JsonResult(new Book() { Code = "", 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.总体流程

通过下图 来看一下总体的流程:

图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 ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{
var actionResult = (IActionResult)executor.Execute(controller, arguments);
EnsureActionResultNotNull(executor, actionResult);
return new ValueTask<IActionResult>(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 ValueTask<IActionResult> Execute(
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<IActionResult>(actionResult);
} // Catch-all for sync methods
protected 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<T> 类型就是这样,该例会介绍。

所以,针对不同类型的Action,系统设置了多种XXXResultExecutor来处理,最终结果无论是什么,都会被转换成IActionResult类型。方便在图 17‑1所示的第三部分进行IActionResult的执行。

上一节列出了多种不同的Action,它们的处理在这里就不一一讲解了。通过下图 17‑2看一下它们的处理结果:

图 2

这里有void类型没有讲到,它本身没有返回结果,但它会被赋予一个结果EmptyResult,它也是ActionResult的子类。

图 2被两行虚线分隔为三行,第一行基本都介绍过了,第二行是第一行对应的异步方法,上一节介绍常见的返回类的时候说过,这些异步方法的返回结果和对应的同步方法是一样的。不过通过图 2可知,处理它们的XXXResultExecutor方法是不一样的。

第三行的ActionResult<T> 类型是在ASP.NET Core 2.1 引入的,它支持IActionResult的子类也支持类似string和Book这样的特定类型。

public sealed class ActionResult<TValue> : IConvertToActionResult
{
public ActionResult(TValue value)
{
if (typeof(IActionResult).IsAssignableFrom(typeof(TValue)))
{
var error = Resources.FormatInvalidTypeTForActionResultOfT(typeof(TValue), "ActionResult<T>"); throw new ArgumentException(error);
} Value = value;
} public ActionResult(ActionResult result)
{
if (typeof(IActionResult).IsAssignableFrom(typeof(TValue)))
{
var error = Resources.FormatInvalidTypeTForActionResultOfT(typeof(TValue), "ActionResult<T>");
throw new ArgumentException(error);
} Result = result ?? throw new ArgumentNullException(nameof(result));
} /// <summary>
/// Gets the <see cref="ActionResult"/>.
/// </summary>
public ActionResult Result { get; } /// <summary>
/// Gets the value.
/// </summary>
public TValue Value { get; } public static implicit operator ActionResult<TValue>(TValue value)
{
return new ActionResult<TValue>(value);
} public static implicit operator ActionResult<TValue>(ActionResult result)
{
return new ActionResult<TValue>(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<T> 类型返回的结果被加上引号的意思就是结果类型可能是直接返回的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 = "", 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-
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);
}
}

由图 2可知,虽然所有类型的Action的结果都被转换成了IActionResult,但它们本质上还是有区别的。所以这个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<JsonResultExecutor>(); 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
{
/// <inheritdoc />
public override void ExecuteResult(ActionContext context)
{ }
}

5. 下集预告

对于以上几种类型返回结果的格式是固定的,JsonResult就会返回JSON格式,ViewResult就会返回Html格式。

但是从第一节的例子可知,string类型会返回string类型的字符串,而Book这样的实体类型却会返回JSON。

由图 2可知这两种类型在执行完毕后,都被封装成了ObjectResult,那么ObjectResult在执行的时候又是如何被转换成string和JSON两种格式的呢?

下一章继续这个话题。

ASP.NET Core 2.2 : 二十. Action的多数据返回格式处理机制的更多相关文章

  1. ASP.NET Core 2.2 : 二十六. 应用JWT进行用户认证

    本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新方案(ASP.NET Core 系列目录) 一.什么是JWT? JWT(json web token ...

  2. ASP.NET Core 2.2 : 二十二. 多样性的配置方式

    大多数应用都离不开配置,本章将介绍ASP.NET Core中常见的几种配置方式及系统内部实现的机制. 说到配置,第一印象可能就是“.config”类型的xml文件或者“.ini”类型的ini文件,在A ...

  3. ASP.NET Core 3.0 : 二十五. TagHelper

    什么是TagHelper?这是ASP.NET Core 中新出现的一个名词,它的作用是使服务器端代码可以在Razor 文件中参与创建和呈现HTML 元素.(ASP.NET Core 系列目录) 一.概 ...

  4. ASP.NET Core 3.0 : 二十四. 配置的Options模式

    上一章讲到了配置的用法及内部处理机制,对于配置,ASP.NET Core还提供了一种Options模式.(ASP.NET Core 系列目录) 一.Options的使用 上一章有个配置的绑定的例子,可 ...

  5. ASP.NET Core 2.2 : 二十六. 应用JWT进行用户认证及Token的刷新

    来源:https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_26.html 本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及T ...

  6. ASP.NET Core 3.0 : 二十八. 在Docker中的部署以及docker-compose的使用

    本文简要说一下ASP.NET Core 在Docker中部署以及docker-compose的使用  (ASP.NET Core 系列目录). 系统环境为CentOS 8 . 打个广告,求职中.. 一 ...

  7. ASP.NET Core 2.2 : 二十三. 深入聊一聊配置的内部处理机制

    上一章介绍了配置的多种数据源被注册.加载和获取的过程,本节看一下这个过程系统是如何实现的.(ASP.NET Core 系列目录) 一.数据源的注册 在上一节介绍的数据源设置中,appsettings. ...

  8. 学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. Python - 自学django,上线一套资产管理系统

    一.概述 终于把公司的资产管理网站写完,并通过测试,然后上线.期间包括看视频学习.自己写前后端代码,用时两个多月.现将一些体会记录下来,希望能帮到想学django做web开发的人.大牛可以不用看了,小 ...

  2. iOS的录屏功能

    iOS的录屏功能其实没什么好说的,因为网上的教程很多,但是网上的Demo无一例外几乎都有一个bug,那就是iPad上会出现闪退,这也体现了国内的教程文档的一个特点,就是抄袭,教程几乎千篇一律,bug也 ...

  3. American daily English notes (enlarged edition): A review

    Life English is the most pragmatic kind of English when one wants to associate with foreigner friend ...

  4. Kafka基本知识入门(一)

    1. 基础知识 有关RabbitMQ,RocketMQ,Kafka的区别这个网上很多,了解一下区别性能,分清什么场景使用.分布式环境下的消息中间件Kafka做的比较不错,在分布式环境下使用频繁,我也不 ...

  5. JavaScript数据结构——队列的实现与应用

    队列与栈不同,它遵从先进先出(FIFO——First In First Out)原则,新添加的元素排在队列的尾部,元素只能从队列头部移除. 我们在前一篇文章中描述了如何用JavaScript来实现栈这 ...

  6. 安装Windows Server 2008

    下面介绍一下,Windows Server操作系统安装,以及在企业中的应用,在小型企业中可以使用不同的版本搭建不同类型的服务,小型企业中可以搭建Web服务,FTP服务,以及DNS和DHCP服务等,在大 ...

  7. Nacos(二):SpringCloud项目中接入Nacos作为注册中心

    前言 通过上一篇文章:Nacos介绍简单了解了Nacos的发展历程和现状,本文我们开始Nacos试水的第一步: 使用Nacos做注册中心 上周末(7.6)Nacos发布了V1.1.0版本,这次更新支持 ...

  8. 委托和lambda表达式,Action和Func

    1.为什么要用委托 我们为什么要有委托?任何东西存在即合理,不合理的也会被时间淘汰掉,委托既然存在肯定有存在的必要,我们来看一下什么时候可以用到委托. 接下来我们有个需求,就是调用一个方法,取出1-1 ...

  9. [HNOI2008]玩具装箱toy(斜率优化dp)

    前言 这是我写的第一道$dp$斜率优化的题目,$dp$一直都很菜,而且咖啡鸡都说了这是基础的东西,然而看别人对$dp$斜率优化一大堆公式又看不懂就老老实实做几道题目,这个比较实在 描述 给出$n$和$ ...

  10. 盘一盘 NIO (一)—— Buffer源码解析

    Buffer是个啥? Buffer 即缓冲区,用来暂存输入输出数据的区域.Buffer对象是一份固定数量的数据的容器,实质上是一个数组.但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问, ...