简体   繁体   中英

How can I get my asp.netcore JsonResult to serialize to body inside my action controller so I can catch exceptions and time its execution?

I have a controller action that looks like this:

[HttpGet]
public async Task<IActionResult> Query(CancellationToken ct, string query) 
{
  var sw = new StopWatch();
  sw.Start();

  try {
    IEnumerable<object> shortInfo = someObject.Query(query, ct).Distinct();
    return new JsonResult(
        // note that the next line still returns an IEnumerable, and does not execute yet
        // shortInfo has not yet begun enumeration either.
        shortInfo.Select(si => SomeSelectFunction(si))
    );
  }
  catch (Exception e)
  {
    return StatusCode(499);
  }
  finally
  {
    sw.Stop();
    Debug.WriteLine($"Query took {sw.ElapsedMilliseconds / 1000.0} sec");
  }
}

When I run my code, the time to execute is very small, and none of the enumeration has yet happened. In addition, I cannot catch any exceptions thrown by someObject.Query due to cancellation, nor any exceptions thrown as a result of SomeSelectFunction() which may also be quite complex.

This has the advantage that JsonResult is enumerated and serialized directly into the response body, without materializing a bunch of objects.

If instead, I change the line inside JsonResult to

        shortInfo.Select(si => SomeSelectFunction(si)).ToList()

Then I can time correctly and catch exceptions, at the cost of materializing the whole list before serializing.

Question: Is there any way to get the benefits of the deferred serialization, and still be able to time the total duration of the query function. Is there a way to serialize the result to the response body inside this function and still return the json as json?

Consider using a Filter Implementation , allowing you to hook into the before and after execution order. eg

   public class EndpointTimingMetricFilter : IActionFilter
   {
      public void OnActionExecuting(ActionExecutingContext context)
      {
         context.HttpContext.Items["timer"] = Stopwatch.StartNew();
      }

      public void OnActionExecuted(ActionExecutedContext context)
      {
         if (!(context.HttpContext.Items["timer"] is Stopwatch sw))
         {
            return;
         }

         sw.Stop();

         var endpointId = GetEndpointId(context);

         Debug.WriteLine($"Query took {sw.ElapsedMilliseconds / 1000.0} sec to execute for endpoint {endpointId}"); 
      }

      private static string GetEndpointId(ActionContext context)
      {
         var template = context.ActionDescriptor.AttributeRouteInfo.Template;
         var method   = context.HttpContext.Request.Method;
         return method + " - " + template;
      }
   }

This then gives you a cross-cutting concern that you can apply to any of your endpoints, and keeps this boiler plate code out of your main controller actions.

OnActionExecuted is after the result/response is built, so should give you what you're after,

在此处输入图像描述

Image from: https://www.c-sharpcorner.com/article/working-with-filters-in-asp-net-core-mvc/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM