ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制

上一章讲了系统如何将客户端提交的请求数据格式化处理成我们想要的格式并绑定到对应的参数,本章讲一下它的”逆过程”,如何将请求结果按照客户端想要的格式返回去。 (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 = "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 Task GetTaskJson()
    {
        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.总体流程

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

ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制

图1

这涉及三部分内容:

第一部分,在invoker的生成阶段。在第14章讲invoker的生成的时候,讲到了Action的执行者的获取,它是从一系列系统定义的 _XXX_ResultExecutor中筛选出来的,虽然它们名为 _XXX_ResultExecutor,但它们都是Action的执行者而不是ActionResult的执行者,都是ActionMethodExecutor的子类。以Action是同步还是异步以及Action的返回值类型为筛选条件,具体这部分内容见图 14‑2所示 _XXX_ResultExecutor列表及其后面的筛选逻辑部分。在图 17‑1中,筛选出了被请求的Action对应的 _XXX_ResultExecutor,若以Home/Index这个默认的Action为例,这个 _XXX_ResultExecutor应该是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的选择与执行

第一部分,系统为什么要定义这么多种 _XXX_ResultExecutor并且在请求的时候一个个筛选合适的 _XXX_ResultExecutor呢?从筛选规则是以Action的同步、异步以及Action的返回值类型来看,这么多种 _XXX_ResultExecutor就是为了处理不同的Action类型。

依然以Home/Index为例,在筛选 _XXX_ResultExecutor的时候,最终返回结果是SyncActionResultExecutor。它的代码如下:

private class SyncActionResultExecutor : ActionMethodExecutor
{
     public override ValueTask Execute(
          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);
}

_XXX_ResultExecutor的CanExecute方法是筛选的条件,通过这个方法判断它是否适合当前请求的目标Action。它要求这个Action不是异步的并且返回结果类型是派生自IActionResult的。而Home/Index这个Action标识的返回结果是IActionResult,实际是通过View()这个方法返回的,这个方法的返回结果类型实际是IActionResult的派生类ViewResult。这样的派生类还有常见的JsonResult和ContentResult等,他们都继承了ActionResult,而ActionResult实现了IActionResult接口。所以如果一个Action是同步的并且返回结果是JsonResult或ContentResult的时候,对应的 _XXX_ResultExecutor也是SyncActionResultExecutor。

第二部分中,Action的执行是在 _XXX_ResultExecutor的Execute方法,它会进一步调用了ObjectMethodExecutor的Execute方法。实际上所有的Action的都是由ObjectMethodExecutor的Execute方法来执行执行的。而众多的 _XXX_ResultExecutor方法的作用是调用这个方法并且对返回结果进行验证和处理。例如SyncActionResultExecutor会通过EnsureActionResultNotNull方法确保返回的结果不能为空。

如果是sting类型呢?它对应的是SyncObjectResultExecutor,代码如下:

private class SyncObjectResultExecutor : ActionMethodExecutor
{
    public override ValueTask 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(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

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

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

ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制

图 2

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

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

第三行的ActionResult

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

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);
    }
}

由图 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();

        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两种格式的呢?

下一章继续这个话题。

本文地址:asp.net core: 二十. action的多种数据返回格式处理机制

(ASP.NET Core 系列目录)

Original: https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_20.html
Author: FlyLolo
Title: ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581862/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Redis持久化原理 — RDB与AOF详细解释

    一、持久化的作用 持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化Redis所有数据保持在内存中,对数据的更新将异步地保存…

    Linux 2023年5月28日
    061
  • pysimpleGui FilesBrowse函数原始说明

    FilesBrowse: (button_text=’Browse’, target=(ThisRow, -1), file_types=((“…

    Linux 2023年6月14日
    065
  • LyScript 从文本中读写ShellCode

    LyScript 插件通过配合内存读写,可实现对特定位置的ShellCode代码的导出,或者将一段存储在文本中的ShellCode代码插入到程序堆中,此功能可用于快速将自己编写的S…

    Linux 2023年5月28日
    089
  • Java高级

    抽象类和抽象方法 1.定义 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。 类的设计应该保证父类和子类都能够共享特征。 有时候将一个父类设计的非常抽象…

    Linux 2023年6月13日
    0103
  • 雷霆传奇H5光柱版游戏详细图文架设教程

    前言 想体验经典传奇的热血PK吗?想体验满级VIP的尊贵吗?想体验一刀99999的爽快吗?各种极品炫酷时装、坐骑、翅膀、宠物通通给你,就在光柱版雷霆传奇H5! 本文讲解雷霆传奇架设…

    Linux 2023年6月7日
    0128
  • 那些技术实战中的架构设计方法

    上个月我写的一篇文章《关于技术能力的思考和总结》引起了大家的关注,好多读者的评论”以写代想、以想促真、以讲验真”,大家的感受很深刻,基于上次的文章,这篇文章…

    Linux 2023年6月8日
    083
  • 《kasini3000》批量修改linux被控机密码

    《卡死你3000》,是开源,免费,跨平台的devops批量脚本框架。 网址:码云家 https://gitee.com/chuanjiao10/kasini3000 批量生成密码之…

    Linux 2023年6月13日
    078
  • vue2和nginx配置Gzip

    减小js和css文件大小,提高首屏速度。虽然也没有快到哪里去(可能是因为我没有调参),但js、css文件的大小都大幅度减小了。参考:https://blog.csdn.net/qq…

    Linux 2023年6月7日
    095
  • npm 和 maven 使用 Nexus3 私服 | 前后端一起学

    前文《Docker 搭建 Nexus3 私服 》介绍了在 docker 环境下安装 nexus3 以及 nexus3 的基本操作和管理,本文分别介绍 npm(前端)和 maven(…

    Linux 2023年6月7日
    065
  • MySQL slow log 慢日志

    sql慢日志用于记录执行时间超过指定阈值的SQL,对于系统性能和故障排错非常有帮助 1.如何开启sql慢日志 –&#x5F00;&#x542F;slow log …

    Linux 2023年6月6日
    071
  • WPF 开源二维绘画小工具 GeometryToolDemo 项目

    这是一个演示 WPF 进行二维绘画的小工具 Demo 项目,基于 MIT 协议在 GitHub 上完全开源 这是一个演示 WPF 进行二维绘画的小工具 Demo 项目,基于 MIT…

    Linux 2023年6月6日
    098
  • ipv6 6r 原理介绍,IPv6 Rapid Deployment, IPv6 6rd, Linux IPv6 6rd初探

    IPv6 Rapid Deployment: Provide IPv6 Access to Customers over an IPv4-Only Network 原文地址:htt…

    Linux 2023年6月6日
    0105
  • Linux之Nginx入门

    一、Nginx介绍 Nginx是一个开源且高性能、可靠的http web服务、代理服务。 开源:直接获取源代码 高性能:支持海量并发 可靠:服务稳定 高性能,高并发 Nginx支持…

    Linux 2023年5月27日
    086
  • 高速USB转8串口产品设计-RS485串口

    基于480Mbps 高速USB转8路串口芯片CH348,可以为各类主机扩展出8个独立的串口。使用厂商提供的VCP串口驱动程序,可支持Windows、Linux、Android、ma…

    Linux 2023年6月7日
    0110
  • 【XML】学习笔记第二章-dtd

    XML-DTD DTD语句 基本声明语句 引用外部DTD DTD元素 四种元素类型 元素定义关键字 修饰符号 DTD中的属性 属性修饰 属性类型 DTD中的实体和符号 符号 坑 X…

    Linux 2023年6月14日
    094
  • 《闲扯Redis十一》Redis 有序集合对象底层实现

    一、前言 Redis 提供了5种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每种数据类型的特点对于redis的开…

    Linux 2023年5月28日
    085
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球