I'm using TPL to send emails to the end-users without delaying the api response, i'm not sure which method should be used since im dealing with the db context here. I did method 2 because i wasn't sure that the db context would be available by the time the task gets to run, so a created a new EF object, or maybe im doing it all wrong.
public class OrdersController : ApiController {
private AllegroDMContainer db = new AllegroDMContainer();
public HttpResponseMessage PostOrder(Order order) {
// Creating a new EF object and adding it to the database
Models.Order _order = new Models.Order{ Name = order.Name };
db.Orders.Add(_order);
/* Method 1 */
Task.Factory.StartNew(() => {
_order.SendEmail();
});
/* Method 2 */
Task.Factory.StartNew(() => {
Models.Order rOrder = db.Orders.Find(_order.ID);
rOrder.SendEmail();
});
return Request.CreateResponse(HttpStatusCode.Created);
}
}
Both methods are wrong, because you're starting a fire-and-forget operation on a pool thread inside the ASP.NET process.
The problem is, an ASP.NET host is not guaranteed to stay alive between handling HTTP responses. E.g., it can be automatically recycled, manually restarted or taken out of the farm. In which case, the send-mail operation would never get completed and you wouldn't get notified about it.
If you need to speed up the response delivery, consider outsourcing the send-mail operation to a separate WCF or Web API service. A related question: Fire and forget async method in asp.net mvc.
Related
I'm trying to create a Microsoft Graph subscription inside of Startup.ConfigureServices(), but this requires an async operation.
After doing some research it turns out that .NET does not and probably will never support async operations inside of Startup. At this point I'm just willing to block the thread to create the subscription.
However, I have ran into another issue. The controllers are not set up inside of Startup.ConfigureServices even though I am creating the subscription after calling services.AddControllers(). This means that the Graph API is never receiving the 200 from my controller which it needs to register the subscription since the controller hasn't been set up yet I'm assuming.
Is what I am trying to accomplish even possible with the current structure of the .NET framework?
The only workaround I've found is to create the Subscription object without calling the Graph API, call services.AddSingleton<Subscription(provider => subscription), dependency inject the Subscription object into my controller, and then create the subscription by calling the Graph API via an API endpoint inside the controller. Then I must update all of the fields for the dependency injected subscription, since I can't overwrite the object itself with subscription = await _gsc.Subscriptions.Request().AddAsync(_sub); (_gsc being an instance of GraphServiceClient).
This is what I would like to do:
Startup.ConfigureServices()
Task<Subscription> subscriptionTask = gsc.Subscriptions.Request().AddAsync(subscription);
subscription = subscriptionTask.GetAwaiter().GetResult();
services.AddSingleton<GraphServiceClient>(provider => gsc);
services.AddSingleton<Subscription>(provider => subscription);
This is my current workaround:
Startup.ConfigureServices()
services.AddSingleton<GraphServiceClient>(provider => gsc);
services.AddSingleton<Subscription>(provider => subscription);
One of my controller endpoints
public async Task<IActionResult> CreateMSGraphSubscription()
{
try
{
Subscription newSubscription = await _gsc.Subscriptions.Request().AddAsync(_sub);
// Update our singleton instance.
_sub.Id = newSubscription.Id;
_sub.AdditionalData = newSubscription.AdditionalData;
_sub.ApplicationId = newSubscription.ApplicationId;
_sub.CreatorId = newSubscription.CreatorId;
_graphRenewalSettings.IsActive = true;
}
catch (Exception e)
{
return new BadRequestObjectResult(e);
}
return new OkObjectResult(_sub);
}
My problem is that I have to call one of my API endpoints after the application has started, rather than having the graph subscription ready when the app has started.
UPDATE
I figured out that I need to inject into Startup.Configure() IHostApplicationLifetime lifetime and then use
lifetime.ApplicationStarted.Register(async () =>
{
Subscription newSubscription = await gsc.Subscriptions.Request().AddAsync(sub);
// Update our singleton instance.
sub.Id = newSubscription.Id;
sub.AdditionalData = newSubscription.AdditionalData;
sub.ApplicationId = newSubscription.ApplicationId;
sub.CreatorId = newSubscription.CreatorId;
graphRenewalSettings.IsActive = true;
});
at the end of Startup.Configure() to register the graph subscription with the Graph API once the app has started up. (sub and gsc are my Subscription and GraphServiceClient objects, respectively, and I inject these into Startup.Configure() also)
This is not ideal since I would like to inject an already registered Subscription object instead of updating all of the fields like I do above, but I could not find a better way to do this.
I have a controller where the functionality required is to implement a call where two actions are done simultaneously, first we get input and do a call to an external application then respond to the call OK we are working on it and release the caller. When the external application responds, we get the response and save to the db, I am using a task.delay as
Part 1
[HttpPost]
public async Task<IActionResults> ProcessTransaction(Transactions transactions)
{
// do some processing
TransactionResults results = new TransactionResults();
Notify(transactions, results);
return Ok("We are working on it, you will get a notification");
}
The delayed task
private void Notify(Transactions transactions, TransactionResults results)
{
Task.Delay(10000).ContinueWith(t => SendNotification(transactions, results));
}
on the SendNotification I am attempting to save the results
private void SendNotification(Transactions transactions, TransactionResults results)
{
// some processing
_context.Add(results); // this gives an error context has already been disposed
_context.SaveChanges();
}
Is there a better way to do this, or a way to re instantiate the context?
I managed to do a work around to the problem I am facing, I created an endpoint that I would call once the notification results came back and the data would be saved on the callback not at that particular event. Once the controller has respond with an Ok, the controller is disposed and its difficult to re instantiate it. The call back work around works for now, I will update if I find another way to do it.
I have an ASP.NET Core web app, with WebAPI controllers. All I am trying to do is, in some of the controllers, be able to kick off a process that would run in the background, but the controller should go ahead and return before that process is done. I don't want the consumers of the service to have to wait for this job to finish.
I have seen all of the posts about IHostedService and BackgroundService, but none of them seem to be what I want. Also, all these examples show you how to set things up, but not how to actually call it, or I am not understanding some of it.
I tried these, but when you register an IHostedService in Startup, it runs immediately at that point in time. This is not what I want. I don't want to run the task at startup, I want to be able to call it from a controller when it needs to. Also, I may have several different ones, so just registering services.AddHostedService() won't work because I might have a MyServiceB and MyServiceC, so how do I get the right one from the controller (I can't just inject IHostedService)?
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
You have the following options:
IHostedService classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events. This queue system can be as simple as a Singleton class with a ConcurrentQueue that you pass in to your controller, or something like an IDistributedCache or more complex external pub/sub systems. Then you can just poll the queue in your IHostedService and run certain operations based on it. Here is a microsoft example of IHostedService implementation for handling queues https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks
Note that the Singleton class approach can cause issues in multi-server environments.
Example implementation of the Singleton approach can be like:
// Needs to be registered as a Singleton in your Startup.cs
public class BackgroundJobs {
public ConcurrentQueue<string> BackgroundTasks {get; set;} = new ConcurrentQueue<string>();
}
public class MyController : ControllerBase{
private readonly BackgroundJobs _backgroundJobs;
public MyController(BackgroundJobs backgroundJobs) {
_backgroundJobs = backgroundJobs;
}
public async Task<ActionResult> FireAndForgetEndPoint(){
_backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
}
}
public class MyBackgroundService : IHostedService {
private readonly BackgroundJobs _backgroundJobs;
public MyBackgroundService(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public void StartAsync(CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
if(_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
{
// Code to do long running operation
}
Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
}
}
}
Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
IServiceProvider _serviceProvider;
public async Task<ActionResult> FireAndForgetEndPoint()
{
// Do stuff
_ = FireAndForgetOperation(_serviceProvider);
Return Ok();
}
public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
{
using (var scope = _serviceProvider.CreateScope()){
await Task.Delay(1000);
//... Long running tasks
}
}
Update: Here is the Microsoft example of doing something similar: https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads
As I understand from your question you want to create a fire and forget task like logging to database. In this scenario you don't have to wait for log to be inserted database. It also took much of my time to discover an easily implementable solution. Here is what I have found:
In your controller parameters, add IServiceScopeFactory. This will not effect the request body or header. After that create a scope and call your service over it.
[HttpPost]
public IActionResult MoveRecordingToStorage([FromBody] StreamingRequestModel req, [FromServices] IServiceScopeFactory serviceScopeFactory)
{
// Move record to Azure storage in the background
Task.Run(async () =>
{
try
{
using var scope = serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<ICloudStorage>();
await repository.UploadFileToAzure(req.RecordedPath, key, req.Id, req.RecordCode);
}
catch(Exception e)
{
Console.WriteLine(e);
}
});
return Ok("In progress..");
}
After posting your request, you will immediately receive In Progress.. text but your task will run in the background.
One more thing, If you don't create your task in this way and try to call database operations you will receive an error like this which means your database object is already dead and you are trying to access it;
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
My code is based on Repository pattern. You should not forget to inject service class in your Startup.cs
services.AddScoped<ICloudStorage, AzureCloudStorage>();
Find the detailed documentation here.
What is the simplest way to run a single background task from a controller in .NET Core?
I don't want the consumers of the service to have to wait for this job to finish.
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
The problem is that ASP.NET is a framework for writing web services, which are applications that respond to requests. But as soon as your code says "I don't want the consumers of the service to have to wait", then you're talking about running code outside of a request (i.e., request-extrinsic code). This is why all solutions are complex: your code has to bypass/extend the framework itself in an attempt to force it to do something it wasn't designed to do.
The only proper solution for request-extrinsic code is to have a durable queue with a separate background process. Anything in-process (e.g., ConcurrentQueue with an IHostedService) will have reliability problems; in particular, those solutions will occasionally lose work.
I've taken over a code base from someone else. This is a web application built on Angular 8 (client) and .NET Core 3.0 (server).
Brief description of the application:
Frequent notifications are stored in a database, with an SqlTableDependency attached to it for detecting new notifications.
When new notifications occur, the server prompts all clients to request an updated list based on their custom filters. These client-to-server requests happen over HttpPost with the filter as a parameter.
The problem occurs when too many notifications arrive at once. Say, when 10 new notifications arrive, the server sends 10 update prompts to the client at the same time, causing the client to immediately send 10 HttpPost requests to the API.
The API takes the filter from the POST, uses it to query the database, and returns the filtered result to the calling client. However, when 10 of these arrive at the same time, it causes a DbContext error - more specific:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext.
public class AlarmController : Controller
{
private readonly IAlarmRepository alarmRepo;
private readonly ISiteRepository siteRepo;
public AlarmController(IAlarmRepository alarmRepo, ISiteRepository siteRepo)
{
this.alarmRepo = alarmRepo;
this.siteRepo = siteRepo;
}
[HttpPost("filter")]
public async Task<IActionResult> FilterAlarm([FromBody] AlarmRequest alarmRequest)
{
var snmpReciverList = await this.alarmRepo.GetFilteredSNMPReceiverHistory(alarmRequest.FromDate, alarmRequest.ToDate);
var siteList = await this.siteRepo.GetSiteListFiltered(int.Parse(alarmRequest.Filter), alarmRequest.SiteName);
return Ok(await SNMPHistoryMapping.DoMapping(siteList, snmpReciverList);
}
This HttpPost returns an Ok() with a list of the data requested, in which some mapping is done:
IEnumerable<Site> sites = siteList;
IEnumerable<SnmpreceiverHistory> histories = snmpReceiverList;
IEnumerable<SNMPHistoryResponse> data = (from s in sites
join rh in histories on s.Address equals rh.Ipaddress
where priority > 0 ? s.SitePriority == priority : true
&& !string.IsNullOrEmpty(trap) ? rh.AlarmDescription.Contains(trap) : true
select new SNMPHistoryResponse()
{
AlarmDescription = rh.AlarmDescription,
EventType = rh.EventType,
OnOffStatus = rh.OnOffStatus,
ParentSiteName = TraceFullParentDescription(s.Parent),
ReceiveTime = rh.ReceiveTime,
RepeatCount = rh.RepeatCount,
SiteName = s.Description,
SitePriority = s.SitePriority,
Status = AlarmStatus.GetStatusDescription(rh.EventType),
Value = rh.Value
});
When multiple of these [HttpPost("filter")] requests arrive at the same time, it appears as if a new thread is created for each one. They all connect on the same DbContext, and the next query starts before the previous is completed.
I can solve it by putting delays between each request from the client, but I want a more robust server-side solution to it, effectively processing these specific requests sequentially.
Note that this is EF Core and .NET Core 3.0, which does not have a SynchronizationContext.
I believe the comment posted by Panagiotis Kanavos is correct:
In this case a single DbContext is created by dependency injection, don't do that. All examples and tutorials show that the DbContexts are Scoped.
This catches me often, and actually just did. I wasn't using dependency injection, but sharing the DbContext around because I was being lazy. Best to properly set up dependency injection and do it the right way, e.g.:
IHostBuilder host = CreateHostBuilder(args);
host.ConfigureServices(services => {
services.AddSingleton(service);
// other stuff...
// Then the context:
services.AddScoped<DataContext>(x => {
DbContextOptionsBuilder<DataContext> dbBuilder =
new DbContextOptionsBuilder<DataContext>();
dbBuilder.UseNpgsql(connstr);
return new DataContext(dbBuilder.Options);
});
});
// Start the host
host.Build().Run();
The documentation for AddScoped is here, and in true microsoft form, it is impossible to read or digest. Stackoverflow does a better job at explaining it.
I'm in a situation where two calls at the same time write to the session (of an asp.net core application running on the old framework), and one of the session variables gets overwritten.
Given the following controller code, assume that the long session gets called first, 200 ms later the short session gets called, and 800 ms later (when the long session is done) the result of both sessions gets called.
[HttpPost("[action]")]
public async Task<IActionResult> TestLongSession() {
HttpContext.Session.SetString("testb", "true");
// If we do this delay BEFORE the session ("testb") is set, then all is fine.
await Task.Delay(1000);
return Ok();
}
[HttpPost("[action]")]
public async Task<IActionResult> TestShortSession() {
HttpContext.Session.SetString("testa", "true");
return Ok();
}
[HttpGet("[action]")]
public async Task<IActionResult> TestResultOfBothSessions() {
string a = HttpContext.Session.GetString("testa");
string b = HttpContext.Session.GetString("testb");
return Ok($"A: {a}, B: {b}");
}
The result of the final call (TestBothSessions) is "A: , B: true".
The question is then: Is there something I missed to make the session work (aka, return "A: true, B: true")?
Obviously, I could remove the delay and all is fine, but in the real application there's a call that potentially can take some time, and I prefer not to write the session variable at a later time (I guess I could with a bit of custom error handling, but then the problem still remains that I no longer trust the asp.net session to work with synchronous calls).
Edit: The typescript code that calls these endpoints from the browser:
this.service.testLongSession().subscribe(() => {
this.service.testBothSessions().subscribe((result: string) => {
console.log(result);
});
});
setTimeout(() => {
this.service.testShortSession().subscribe();
}, 200);
I believe the behavior you observe is what the ASP.NET authors intended. I look at the interfaces that session stores need to implement, namely ISession and ISessionStore, and I see no synchronization mechanisms to prevent the overwriting of data during simultaneous requests.
The benefit of such a simple interface is that it's much easier to implement, and can be easily implemented by a variety of caches and databases.
ASP.NET 4 had a much more complex session store base class SessionStateStoreProviderBase that included locking logic, but it was really challenging to implement.