Marco
Marco

Reputation: 61

.net core 3 web api controller - code execution after sending response

I have a .net core 3.1 webapi project. In some controllers, I need to execute some code that doesn't impact the response to the client.

In particular, I have a method that returns a json with a profile's information, which is called when user visits that profile's page. After get profile's information, I need to log that a user visits this page, but, for a faster response, I want to return response before this log operation.

[Route("[controller]")]
[ApiController]
public class ProfilesController : ControllerBase
    {
        [HttpGet("{id}")]
        public async Task<ActionResult<Profile>> GetProfileById([FromRoute] int id)
        {
             Profile profile = await _profileService.GetByIDAsync(id);

             // DO THIS OPERATION IN BACKGROUND AND DON'T WAIT TO RETURN RESPONSE
              await _logService.LogProfileVisit(id);

             return Ok(profile);
        }
}

How can I do it?

Thanks.

Upvotes: 3

Views: 2710

Answers (5)

Andrei Istrate
Andrei Istrate

Reputation: 27

There are 2 possible solutions:

  1. Use a framework such as HangFire or Quartz Scheduler that brings this functionality. This might make sense if you'll be needing to use this feature in more than a few places. Otherwise, they're rather heavy.
  2. Use Task.Run() in conjuction with ScopeFactory.

For the second option, the basic code would look like this:

[HttpGet("{id}")]
public async Task<ActionResult<Profile>> GetProfileById([FromRoute] int id)
{
     Profile profile = await _profileService.GetByIDAsync(id);

     Task.Run(async () =>
     {
         await _logService.LogProfileVisit(id);
     }

     return Ok(profile);
}

This will crash. As soon as the controller returns it will dispose of all the services, including _logService' leading to an error. The solution is to use IServiceScopeFactory to create a scoped service that will be disposed when the task ends:

[HttpGet("{id}")]
public async Task<ActionResult<Profile>> GetProfileById([FromRoute] int id)
{
     Profile profile = await _profileService.GetByIDAsync(id);

     Task.Run(async () =>
     {
         using var scope = _serviceScopeFactory.CreateScope();
         var _logService = scope.ServiceProvider.GetService<LogService>();
         await _logService.LogProfileVisit(id);
     }

     return Ok(profile);
}

For a complete example of this implementation see this post.

Upvotes: 1

Eng Hazymeh
Eng Hazymeh

Reputation: 147

You can use HangFire or Quartz Scheduler to run task in background without waiting response.

Example using HangFire :

var jobId = BackgroundJob.Enqueue(
    () => _logService.LogProfileVisit(id));

Upvotes: 0

zekeriya kocairi
zekeriya kocairi

Reputation: 346

You can use many approach but there is no single line solution for that. You can use background workers to achive that. "Bakcground service with hosted Service in .NET Core" may suit your needs. You can also use a library like Hangfire for easily configurable complicated solutions.

Upvotes: 0

Farhad Zamani
Farhad Zamani

Reputation: 5861

Remove await keyword before _logService.LogProfileVisit(id);

This will prevent you from waiting for response

[Route("[controller]")]
[ApiController]
public class ProfilesController : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<Profile>> GetProfileById([FromRoute] int id)
    {
        _logService.LogProfileVisit(id);
        Profile profile = await _profileService.GetByIDAsync(id);
        return Ok(profile);
    }
}

Upvotes: 1

glabek94
glabek94

Reputation: 71

You can do this with filter for example. Read here

Upvotes: 0

Related Questions