I have an ASP.NET Core website, using EFCore. I would like to do some work like logging to the database, but after having sent the response to the user in order to answer faster
There's no out of the box way to do what you want.
But, here's a possible approach:
Since the worked runs somewhere else and not on the request thread, the server can complete the request thread and the worker can do what's left.
Try using Hangfire. Hangfire is an easy way to perform background processing in .NET and .NET Core applications. No Windows Service or separate process required. Backed by persistent storage. Open and free for commercial use.
You could do something like
var jobId = BackgroundJob.Enqueue(() => Log(model));
And here is my blog post on using HangFire in ASP.NET Core
Create a new class that inherits from ActionFilterAttribute
, overwrite the OnResultExecuted
method to perform the logging and then apply your attribute class to the controller actions you want to do logging.
Building on Jeans answer and a question and answer on the try - return - finally
pattern, the try
and finally
blocks can be removed (if you don't really want to catch an exception).
This leeds to the following code:
public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)
{
var newModel = new ResponseViewModel(model);
// Some work
Response.OnCompleted(async () =>
{
// Do some work here
await Log(model);
});
return View("RequestView",newModel);
}
I see this has never been answered, but actually have a solution. The simple solution:
public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)
{
try
{
var newModel = new ResponseViewModel(model);
// Some work
return View("RequestView",newModel)
}
finally
{
Response.OnCompleted(async () =>
{
// Do some work here
await Log(model);
});
}
}
The secure solution, as OnCompleted
used to be called before the response being sent, so delaying the response:
public static void OnCompleted2(this HttpResponse resp, Func<Task> callback)
{
resp.OnCompleted(() =>
{
Task.Run(() => { try { callback.Invoke(); } catch {} });
return Task.CompletedTask;
});
}
and call Response.OnCompleted2(async () => { /* some async work */ })