ibcadmin 发表于 2019-9-12 16:24:13

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

<p>上一章讲了体系怎样将客户端提交的请求数据格式化处置惩罚成我们想要的格式并绑定到对应的参数,本章讲一下它的“逆过程”,怎样将请求结果按照客户端想要的格式返回去。(ASP.NET Core 系列目录)</p>
<h1>一、常见的返回类型</h1>
<p>以体系模板默认天生的Home/Index这个Action来说,为什么当请求它的时候回返回一个Html页面呢?除了这之外,还有JSON、文本等类型,体系是如那边理这些差异的类型的呢?</p>
<p>首先来说几种常见的返回类型的例子,并用Fiddler请求这几个例子看一下结果,涉及到的一个名为Book的类,代码为:</p>


    public class Book
    {
      public string Code { get; set; }
      public string Name { get; set; }
    }


<h2>1.ViewResult类型</h2>


public class HomeController : Controller
{
    public IActionResult Index()
    {
      return View();
    }
}


<p>返回一个Html页面,Content-Type值为: text/html; charset=utf-8</p>
<h2>2.string类型</h2>


    public string GetString()
    {
      return "Hello Core";
    }


<p>返回字符串“Hello Core”,Content-Type值为:text/plain; charset=utf-8</p>
<h2>3.JSON类型</h2>


    public JsonResult GetJson()
    {
      return new JsonResult(new Book() { Code = "1001", Name = "ASP" });
    }


<p align="left">返回JSON,值为:</p>

<p >{"code":"1001","name":"ASP"}</p>

<p>Content-Type值为:Content-Type: application/json; charset=utf-8</p>
<h2>4.直接返回实体类型</h2>


public Book GetModel()
{
      return new Book() { Code = "1001", Name = "ASP" };
}


<p align="left">同样是返回JSON,值为:</p>

<p >{"code":"1001","name":"ASP"}</p>

<p>Content-Type值同样是:Content-Type: application/json; charset=utf-8</p>
<h2>5.void类型</h2>


public void GetVoid()
{
}


<p>没有返回结果也没有Content-Type值。</p>
<p><strong>下面看几个异步方法:</strong></p>
<h2>6.Task类型</h2>


    public async Task GetTaskNoResult()
    {
      await Task.Run(() => { });
    }


<p>与void类型一样,没有返回结果也没有Content-Type值。</p>
<h2>7.Task<string>类型</h2>


    public async Task<string> GetTaskString()
    {
      string rtn = string.Empty;
      await Task.Run(() => { rtn = "Hello Core"; });

      return rtn;
    }


<p>与string类型一样,返回字符串“Hello Core”,Content-Type值为:text/plain; charset=utf-8</p>
<h2>8.Task<JsonResult>类型</h2>


    public async Task<JsonResult> GetTaskJson()
    {
      JsonResult jsonResult = null;
      await Task.Run(() => { jsonResult = new JsonResult(new Book() { Code = "1001", Name = "ASP" }); });
      return jsonResult;
    }


<p align="left">与JSON类型一样,返回JSON,值为:</p>

<p >[{"code":"1001","name":"ASP"},{"code":"1002","name":"Net Core"}]</p>

<p>Content-Type值为:Content-Type: application/json; charset=utf-8</p>
<p>还有其他类型,这里暂不列举了,总结一下:</p>
<ol>
<li>返回结果有空、Html页面、普通字符串、JSON字符串几种。</li>
<li>对应的Content-Type类型有空、text/html、text/plain、application/json几种。</li>
<li>异步Action的返回结果,和其对应的同步Action返回结果类型同等。</li>
</ol>
<p>下一节我们看一下体系是如那边理这些差异的类型的。</p>
<h1>二、内部处置惩罚机制分析</h1>
<h2>1.总体流程</h2>
<p>通过下图 来看一下总体的流程:</p>
<p > <div align="center"></div></p>
<pstyle="text-align: center;">图1</p>
<p>这涉及三部分内容:</p>
<p>第一部分,在invoker的天生阶段。在第14章讲invoker的天生的时候,讲到了Action的实行者的获取,它是从一系列体系界说的<em>XXX</em>ResultExecutor中筛选出来的,虽然它们名为<em>XXX</em>ResultExecutor,但它们都是Action的实行者而不是ActionResult的实行者,都是ActionMethodExecutor的子类。以Action是同步还是异步以及Action的返回值类型为筛选条件,具体这部分内容见图 142所示<em>XXX</em>ResultExecutor列表及其反面的筛选逻辑部分。在图 171中,筛选出了被请求的Action对应的<em>XXX</em>ResultExecutor,若以Home/Index这个默认的Action为例,这个<em>XXX</em>ResultExecutor应该是SyncActionResultExecutor。</p>
<p>第二部分,在Action Filters的处置惩罚阶段。这部分内容见16.5 Filter的实行,此处恰好以Action Filter为例讲了Action Filter的实行方式及Action被实行的过程。在这个阶段,会调用上文筛选出的SyncActionResultExecutor的Execute方法来实行Home/Index这个 Action。实行结果返回一个IActionResult。</p>
<p>第三部分,在Result Filters的处置惩罚阶段。这个阶段和Action Filters的逻辑相似,只不外前者的核心是Action的实行,后者的核心是Action的实行结果的实行。二者都分为OnExecuting和OnExecuted两个方法,这两个方法也都在其对应的核心实行方法前后实行。</p>
<p>团体流程是如许,下面看一下细节。</p>
<h2>2. ActionMethodExecutor的选择与实行</h2>
<p>第一部分,体系为什么要界说这么多种<em>XXX</em>ResultExecutor而且在请求的时候一个个筛选符合的<em>XXX</em>ResultExecutor呢?从筛选规则是以Action的同步、异步以及Action的返回值类型来看,这么多种<em>XXX</em>ResultExecutor就是为了处置惩罚差异的Action类型。</p>
<p>依然以Home/Index为例,在筛选<em>XXX</em>ResultExecutor的时候,终极返回结果是SyncActionResultExecutor。它的代码如下:</p>


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


<p><em>XXX</em>ResultExecutor的CanExecute方法是筛选的条件,通过这个方法判定它是否得当当前请求的目的Action。它要求这个Action不是异步的而且返回结果类型是派生自IActionResult的。而Home/Index这个Action标识的返回结果是IActionResult,实际是通过View()这个方法返回的,这个方法的返回结果类型实际是IActionResult的派生类ViewResult。如许的派生类还有常见的JsonResult和ContentResult等,他们都继续了ActionResult,而ActionResult实现了IActionResult接口。以是假如一个Action是同步的而且返回结果是JsonResult或ContentResult的时候,对应的<em>XXX</em>ResultExecutor也是SyncActionResultExecutor。</p>
<p>第二部分中,Action的实行是在<em>XXX</em>ResultExecutor的Execute方法,它会进一步调用了ObjectMethodExecutor的Execute方法。实际上所有的Action的都是由ObjectMethodExecutor的Execute方法来实行实行的。而浩繁的<em>XXX</em>ResultExecutor方法的作用是调用这个方法而且对返回结果进行验证和处置惩罚。比方SyncActionResultExecutor会通过EnsureActionResultNotNull方法确保返回的结果不能为空。</p>
<p>假如是sting类型呢?它对应的是SyncObjectResultExecutor,代码如下:</p>


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;

}


<p>由于string不是IActionResult的子类,以是会通过ConvertToActionResult方法对返回结果returnValue进行处置惩罚。</p>


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


<p>假如returnValue是IActionResult的子类,则返回returnValue,否则调用一个Convert方法将returnValue转换一下:</p>


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


<p>这个方法会判定returnValue是否实现了IConvertToActionResult接口,假如是则调用该接口的Convert方法转换成IActionResult类型,否则会将returnValue封装成ObjectResult。ObjectResult也是ActionResult的子类。下文有个ActionResult<T> 类型就是如许,该例会介绍。</p>
<p>以是,针对差异类型的Action,体系设置了多种<em>XXX</em>ResultExecutor来处置惩罚,终极结果无论是什么,都会被转换成IActionResult类型。方便在图 171所示的第三部分进行IActionResult的实行。</p>
<p>上一节列出了多种差异的Action,它们的处置惩罚在这里就不一一讲解了。通过下图 172看一下它们的处置惩罚结果:</p>
<p > <div align="center"></div></p>
<p align="center">图 2</p>
<p>这里有void类型没有讲到,它自己没有返回结果,但它会被赋予一个结果EmptyResult,它也是ActionResult的子类。</p>
<p>图 2被两行虚线分隔为三行,第一行基本都介绍过了,第二行是第一行对应的异步方法,上一节介绍常见的返回类的时候说过,这些异步方法的返回结果和对应的同步方法是一样的。不外通过图 2可知,处置惩罚它们的<em>XXX</em>ResultExecutor方法是不一样的。</p>
<p>第三行的ActionResult<T> 类型是在ASP.NET Core 2.1 引入的,它支持IActionResult的子类也支持类似string和Book如许的特定类型。</p>


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


<p>TValue不支持IActionResult及其子类。它的值如果IActionResult子类,会被赋值给Result属性,否则会赋值给Value属性。它实现了IConvertToActionResult接口,想到刚讲解string类型被处置惩罚的时候用到的Convert方法。当返回结果实现了IConvertToActionResult这个接口的时候,就会调用它的Convert方法进行转换。它的Convert方法就是先判定它的值是否是IActionResult的子类,假如是则返回该值,否则将该值转换为ObjectResult后返回。</p>
<p>以是图 2中ActionResult<T> 类型返回的结果被加上引号的意思就是结果类型大概是直接返回的IActionResult的子类,也有大概是string和Book如许的特定类型被封装后的ObjectResult类型。</p>
<h3>3. Result Filter的实行</h3>
<p>结果被统一处置惩罚为IActionResult后,进入图 1所示的第三部分。这部分的重要内容有两个,分别是Result Filters的实行和IActionResult的实行。Result Filter也有OnResultExecuting和OnResultExecuted两个方法,分别在IActionResult实行的前后实行。</p>
<p>自界说一个MyResultFilterAttribute,代码如下:</p>


    public class MyResultFilterAttribute : Attribute, IResultFilter
    {
      public void OnResultExecuted(ResultExecutedContext context)
      {
            Debug.WriteLine("HomeController=======>OnResultExecuted");
      }

      public void OnResultExecuting(ResultExecutingContext context)
      {
            Debug.WriteLine("HomeController=======>OnResultExecuting");
      }
    }


<p>将它注册到第一节JSON的例子中:</p>


   
    public JsonResult GetJson()
    {
      return new JsonResult(new Book() { Code = "1001", Name = "ASP" });
    }


<p>可以看到如许的输出结果:</p>


HomeController=======>OnResultExecuting

……Executing JsonResult……

HomeController=======>OnResultExecuted


<p>在OnResultExecuting中可以通过设置context.Cancel = true;取消反面的工作的实行。</p>


    public void OnResultExecuting(ResultExecutingContext context)
    {
      //用于验证的代码略
      context.Cancel = true;
      Debug.WriteLine("HomeController=======>OnResultExecuting");
    }


<p>再看输出结果就至于的输出了</p>


HomeController=======>OnResultExecuting


<p>同时返回结果也不再是JSON值了,返回结果以及Content-Type全部为空。以是如许设置后,IActionResult以及OnResultExecuted都不再被实行。</p>
<p>在这里除了可以做一些IActionResult实行之前的验证,还可以对HttpContext.Response做一些简朴的操作,比方添加个Header值:</p>


public void OnResultExecuting(ResultExecutingContext context)
{
    context.HttpContext.Response.Headers.Add("version", "1.2");
    Debug.WriteLine("HomeController=======>OnResultExecuting");
}


<p>除了正常返回JSON结果外,Header中会出现新添加的version</p>


Content-Type: application/json; charset=utf-8
version: 1.2


<p>再看一下OnResultExecuted方法,它是在IActionResult实行之后实行。由于在这个方法实行的时候,请求结果已经发送给请求的客户端了,以是在这里可以做一些日志类的操作。举个例子,假如在这个方法中产生了非常:</p>


public void OnResultExecuted(ResultExecutedContext context)
{
      throw new Exception("exception");
      Debug.WriteLine("HomeController=======>OnResultExecuted");
}


<p>请求结果依然会返回正常的JSON,但从输出结果中看到是不一样的</p>


HomeController=======>OnResultExecuting
……
System.Exception: exception


<p>发生非常,反面的Debug输出没有 实行。但却将正确的结果返回给了客户端。</p>
<p>Result Filter介绍完了,看一下核心的IActionResult的实行。</p>
<h2>4. IActionResult的实行</h2>
<p>在ResourceInvoker的case State.ResultInside阶段会调用IActionResult的实行方法InvokeResultAsync。该方法中参数IActionResult result会被调用ExecuteResultAsync方法实行。</p>


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


<p>由图 2可知,虽然所有类型的Action的结果都被转换成了IActionResult,但它们本质上还是有区别的。以是这个IActionResult类型的参数result实际上大概是JsonResult、ViewResult、EmptyResult等具体类型。下面依然以第一节JSON的例子为例来看,它返回了一个JsonResult。在这里就会调用JsonResult的ExecuteResultAsync方法,JsonResult的代码如下:</p>


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


<p>在它的ExecuteResultAsync方法中会获取依靠注入中设置的JsonResultExecutor,由JsonResultExecutor来调用ExecuteAsync方法实行反面的工作。JsonResultExecutor的代码如下:</p>


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


<p>JsonResultExecutor的ExecuteAsync方法的作用就是将JsonResult中的值转换成JSON串并写入context.HttpContext.Response. Body中。至此JsonResult实行完毕。</p>
<p>ViewResult会有对应的ViewExecutor来实行,会通过相应的规则天生一个 Html页面。</p>
<p>而EmptyResult的ExecuteResult方法为空,以是不会返回任何内容。</p>


    public class EmptyResult : ActionResult
    {
      /// <inheritdoc />
      public override void ExecuteResult(ActionContext context)
      {

      }
    }

<h2>5. 下集预告</h2>

<p>对于以上几种类型返回结果的格式是固定的,JsonResult就会返回JSON格式,ViewResult就会返回Html格式。</p>
<p>但是从第一节的例子可知,string类型会返回string类型的字符串,而Book如许的实体类型却会返回JSON。</p>
<p>由图 2可知这两种类型在实行完毕后,都被封装成了ObjectResult,那么ObjectResult在实行的时候又是怎样被转换成string和JSON两种格式的呢?</p>
<p>下一章继续这个话题。</p><br><br/><br/><br/><br/><br/>来源:<a href="https://www.cnblogs.com/FlyLolo/archive/2019/09/11/ASPNETCore2_20.html" target="_blank">https://www.cnblogs.com/FlyLolo/archive/2019/09/11/ASPNETCore2_20.html</a>
页: [1]
查看完整版本: ASP.NET Core 2.2 : 二十. Action的多种数据返回格式处理机制