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.