ASP.NET Core 2: Entity Framework Context is disposed too early in asynchronous PayPal IPN. How do I get it back in a later thread? - paypal

I have an endpoint which is receiving IPN activity from PayPal. Here is the POST Action that is taken straight from their docs with manual modifications:
[HttpPost]
public IActionResult Receive()
{
IPNContext ipnContext = new IPNContext()
{
IPNRequest = Request
};
using (StreamReader reader = new StreamReader(ipnContext.IPNRequest.Body, Encoding.ASCII))
{
ipnContext.RequestBody = reader.ReadToEnd();
}
List<KeyValuePair<string, string>> ipnVarsWithCmd = ipnContext.RequestBody.Split('&')
.Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
//Fire and forget verification task -- ** THIS **
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
//Reply back a 200 code
return Ok();
}
The issue is the indicated line. This is a "fire and forget" route, and is executed asynchronously. When the Action is complete, and returns Ok, I am assuming that the injected Entity Framework context from the controller:
public class IPNController : Controller
{
private readonly EFContext _context;
public IPNController(EFContext context)
{
_context = context;
}
}
... gets Disposed? According to my logs, it looks like it.
Meanwhile, I have that second thread doing the actual legwork of the IPN request which needs that EFContext to be around.
Is there a pattern I am missing here? (Bearing in mind whilst I'm not new to .NET I am to .NET Core)
Or is there a way I can "get it back" so I can use it?
Update:
You might find my initialisation of the context useful:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<EFContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
}

Change:
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
to
await Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
and method declaration to:
public async Task<IActionResult> Receive()
Also wrap IPNContext to using block to let it dispose when it is not needed.

Related

.net core: run big tasks in the background

I created a .net core web api project. It has gotten kinda big and I want to program a "delete" operation which deletes a lot of stuff from the database. Since there are a lot of things to delete, this will be a long running process. So I thought maybe I can run this in the background and just write status updates somewhere for the user to see whats happening.
I googled this and I found BackgroundWorkerQueue and thought this might be my solution.
So I registered the service and everything and here is my method that calls it:
public class DeleteController : ControllerBase {
private readonly BackgroundWorkerQueue _backgroundWorkerQueue;
public AdminController(BackgroundWorkerQueue backgroundWorkerQueue){
_backgroundWorkerQueue = backgroundWorkerQueue;
}
public async Task<ActionResult> HugeDeleteMethod(int id)
{
// some prechecks here...
// and here I thought I'd start the background task
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>
{
var a = _context.StatusTable.Find(id);
a.Status += "Blablablabla\n";
_context.StatusTable.Update(a);
await _context.SaveChangesAsync();
//now start doing delete operations
});
}
}
And that class looks like this:
public class BackgroundWorkerQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
There is also a DeleteService, which is also called in my startup, but I am not sure what it does:
public class DeleteService : BackgroundService
{
private readonly BackgroundWorkerQueue queue;
public NukeService(BackgroundWorkerQueue queue)
{
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Both are added in my startup.cs:
services.AddHostedService<DeleteService>();
services.AddSingleton<BackgroundWorkerQueue>();
Well, maybe I'm going about this all wrong. This is never called it seems, the StatusTable field "Status" is always empty. So how do I do this?
You just need to subclass BackgroundService class or implement IHostedService and than register your service as hosted service.
This will run a service in the background. Than in your service you can leverage the BlockingQueue that will perform tasks only when they are added, e.g. like this:
public class MyService : BackgroundService {
private readonly BlockingCollection<long> queue;
public MyService(){
this.queue = new BlockingCollection<long>();
Task.Run(async () => await this.Execute());
}
public void AddId(long id) {
this.queue.Add(id);
}
private async Task Execute()
{
foreach (var id in this.queue.GetConsumingEnumerable())
{
... do your stuff ...
}
}
}
services.AddHostedService<MyService>();
Here is the docu: Background services in .net core

appsettings.json change connection string on runtime

Asume i have 4 servers with 4 dbs they have all the same catalog.
it is possible to change {serverLocation} on runtime ?
"ConnectionStrings": {
"Euro": "Data Source=campus-db-{serverLocation};Initial Catalog=Shool;Integrated Security=True"}
method should look something like that:
[HttpGet]
public async Task<IActionResult> GetStock(int id, string serverLocation)
{
var queryStock = _context.TblItem.FindAsync(id);
return Ok(queryStock);
}
The idea is that i need only 1 DBContext 1 Model because on all 4 servers the catalog are the same and database structure
you can write like this but i am not sure
IConfiguration _configuration;
public StockManager(IConfiguration configuration)
{
_configuration = configuration;
}
public object FindAsync(int id,serverLocation)
{
//do stuff
_configuration.GetConnectionString($"ConnectionStrings:{serverLocation}");
}
This might help you, please check.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options =>
{
var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
var httpRequest = httpContext.Request;
// Now you can get access to the http context here, do what ever you want to do here with httpRequest.
// write conditional statements here like
if(somecondition-a)
options.UseSqlServer("name=ConnectionStrings:Connection-A"));
if(somecondition-b)
options.UseSqlServer("name=ConnectionStrings:Connection-B"));
}
}
For more infor check this docs
If that do not help, try this approach
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
{
var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
var httpRequest = httpContext.Request;
// Now you can get access to the http context here, do what ever you want to do here with httpRequest.
// write conditional statements here like
if(somecondition-a)
options.UseSqlServer("name=ConnectionStrings:Connection-A"));
if(somecondition-b)
options.UseSqlServer("name=ConnectionStrings:Connection-B"));
}
}

autofac webapi owin integration problems with middleware exection order not correct

I'm confused with Autofac Examples : WebApiExample.OwinSelfHost, the startup class is following:
public class Startup
{
public void Configuration(IAppBuilder app)
{
// In OWIN you create your own HttpConfiguration rather than
// re-using the GlobalConfiguration.
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
var builder = new ContainerBuilder();
// Register Web API controller in executing assembly.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// OPTIONAL - Register the filter provider if you have custom filters that need DI.
// Also hook the filters up to controllers.
builder.RegisterWebApiFilterProvider(config);
builder.RegisterType<CustomActionFilter>()
.AsWebApiActionFilterFor<TestController>()
.InstancePerRequest();
// Register a logger service to be used by the controller and middleware.
builder.Register(c => new Logger()).As<ILogger>().InstancePerRequest();
// Autofac will add middleware to IAppBuilder in the order registered.
// The middleware will execute in the order added to IAppBuilder.
builder.RegisterType<FirstMiddleware>().InstancePerRequest();
builder.RegisterType<SecondMiddleware>().InstancePerRequest();
// Create and assign a dependency resolver for Web API to use.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// The Autofac middleware should be the first middleware added to the IAppBuilder.
// If you "UseAutofacMiddleware" then all of the middleware in the container
// will be injected into the pipeline right after the Autofac lifetime scope
// is created/injected.
//
// Alternatively, you can control when container-based
// middleware is used by using "UseAutofacLifetimeScopeInjector" along with
// "UseMiddlewareFromContainer". As long as the lifetime scope injector
// comes first, everything is good.
app.UseAutofacMiddleware(container);
// Again, the alternative to "UseAutofacMiddleware" is something like this:
// app.UseAutofacLifetimeScopeInjector(container);
// app.UseMiddlewareFromContainer<FirstMiddleware>();
// app.UseMiddlewareFromContainer<SecondMiddleware>();
// Make sure the Autofac lifetime scope is passed to Web API.
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
The FirstMiddleware and SecondMiddleware code was as following:
public class FirstMiddleware : OwinMiddleware
{
private readonly ILogger _logger;
public FirstMiddleware(OwinMiddleware next, ILogger logger) : base(next)
{
this._logger = logger;
}
public override async Task Invoke(IOwinContext context)
{
this._logger.Write("Inside the 'Invoke' method of the '{0}' middleware.", GetType().Name);
await Next.Invoke(context);
}
}
public class SecondMiddleware : OwinMiddleware
{
private readonly ILogger _logger;
public SecondMiddleware(OwinMiddleware next, ILogger logger) : base(next)
{
this._logger = logger;
}
public override async Task Invoke(IOwinContext context)
{
this._logger.Write("Inside the 'Invoke' method of the '{0}' middleware.", GetType().Name);
await Next.Invoke(context);
}
}
According to the comments, the middleware registration order matters. FirstMiddleware first, then SecondMiddleware. but the output was second middleware was invoked first.
the program logs output here
What's wrong with the order?
This is the autofac official example.WebApiExample.OwinSelfHost
Looks like you've found a bug! I've filed an issue about it on your behalf. You can read more technical details about it there, but the short version is that over the years we've changed some Autofac internals to support .NET Core and this looks like something we've missed.
The workaround until this is fixed will be to register the middleware in reverse order, which isn't awesome because once the fix is applied you'll have to reverse them back. :(

Blazor Server - DbContext is disposed after refreshing page

I am developing a Blazor server app, where all clients will have a list of things, any one of these clients can update thing which should then trigger a callback telling all clients to call DbContext.Entry(thing).Reload() so they're up to date. This works all great and well until I refresh the page, then I get the Cannot access a disposed object error and I can't figure out how to get around it.
I have the follow services:
services.AddDbContextPool<MainDbContext>(...);
services.AddSingleton<RefreshService>();
RefreshService.cs:
public class RefreshService {
public Func<long, Task> OnRefreshThing { get; set; }
public void RefreshThing(long thingId) => OnRefreshThing?.Invoke(thingId);
}
Index.blazor:
protected override void OnInitialized() {
RefreshService.OnRefreshIssue += OnRefreshIssue;
}
private async Task OnRefreshThing(long thingId) {
// This works perfectly until I refresh the page & try to call it again
Thing thing = await MainDbContext.Things.FindAsync(thingId); // exception is thrown here
await MainDbContext.Entry(thing).ReloadAsync();
}
And here's an example of what triggers the error:
Thing thing = Things.Where(t => t.ThingId == 1);
thing.Name = "New name";
RefreshService.RefreshThing(thing.ThingId);
You can modify the RefreshService to be Scoped not Singleton
The AddDbContextPool does not dispose the instance of the dbcontext completely, it resets the instance to its default state and it could be because of the reset that it can't access it again

Why am I getting error: "Cannot access disposed object" in .net core 2 with EF and AutoFac?

First the error:
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.
Object name: 'MemberContext'.
I have 3 projects, Domain, API and WebSPA app.
Domain has 2 modules, DomainModule and MediatorModule
public class DomainModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(MemberContext).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf()
.InstancePerLifetimeScope(); // or individually
}
}
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
// enables contravariant Resolve() for interfaces with single contravariant ("in") arg
builder
.RegisterSource(new ContravariantRegistrationSource());
// mediator itself
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
// request handlers
builder
.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t =>
{
object o;
return c.TryResolve(t, out o) ? o : null;
};
})
.InstancePerLifetimeScope();
// notification handlers
builder
.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>) c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
}
}
In API project I have also 2 modules, ApplicationModule and again MediatorModule same as the one above.
public class ApplicationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf().InstancePerLifetimeScope(); // or individually
}
}
No, when I debug I can see that member context gets newed up on each request, yet on second request, it throws above error. To make sure I am not going crazy, I modified constructor of dbcontext to create an id for context so i can verify they are different. What am I doing wrong?
public MemberContext(DbContextOptions<MemberContext> options) : base(options)
{
MemberContextId = Guid.NewGuid();
Console.WriteLine("member context created: " + MemberContextId);
}
Here is the startup in API
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
// .AllowCredentials()
);
});
services.AddMvc()
.AddControllersAsServices();//Injecting Controllers themselves thru DI
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
AddSwaggerGen(services);
//var connection = Configuration["ConnectionString"];
//services.AddDbContext<MemberContext>(options => options.UseSqlServer(connection),ServiceLifetime.Scoped);
services.AddEntityFrameworkSqlServer()
.AddDbContext<MemberContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]
//,sqlServerOptionsAction: sqlOptions =>
//{
// sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
// sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
//}
);
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
var container = new ContainerBuilder();
container.Populate(services);
container.RegisterAssemblyModules(typeof(VIN.Members.Domain.Entities.Member).Assembly,
typeof(Startup).Assembly);
return new AutofacServiceProvider(container.Build());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//NOTE: must be before UseMVC !!!
app.UseCors("CorsPolicy");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
private void AddSwaggerGen(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "VIN Members HTTP API",
Version = "v1",
Description = "Members Service HTTP API",
TermsOfService = "Terms Of Service"
});
});
}
}
UPDATE:
What I am trying to do is delete a record. On client side code looks like this
onDelete(item: IMember) {
//TODO: replace this with dialog service component
if (window.confirm('Are sure you want to delete this member?')) {
//put your delete method logic here
this.service.deleteMember(item).subscribe(x => {
this.getMembers();
});
}
}
this delete request gets mapped to a controller that passes it to mediator
Controller
// DELETE api/members/5
[HttpDelete("{id}")]
public void Delete(Guid id)
{
var command = new DeleteMember.Command(id);
_mediator.Send(command).ConfigureAwait(false);
}
and finally handler
public class DeleteMember
{
public class Command : IRequest
{
public Command(Guid memberId)
{
Guard.NotNull(memberId, nameof(memberId));
MemberId = memberId;
}
public Guid MemberId { get; }
}
public class Handler : AsyncRequestHandler<Command>
{
private MemberContext _context;
public Handler(MemberContext context)
{
_context = context;
Console.WriteLine("Delete member context: " + context.MemberContextId);
}
protected override async Task HandleCore(Command cmd)
{
try
{
var member = await _context.FindAsync<Member>(cmd.MemberId);//.ConfigureAwait(false);
// if (member != null)
//// {
_context.Remove(member);
await _context.SaveChangesAsync().ConfigureAwait(false);
// }
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
As you can see there is no code that disposes that context. Scratching my head.
See this commented out check for member if null. That was throwing error as well, I commented it out just to see what will happen, and now it throws as SaveChangesAsync.
As request completes, context gets disposed. Since command handler uses SaveChangesAsync(), context is disposed before save completes. Culprit is controller method :). It should be async as well.
[HttpDelete("{id}")]
public async Task Delete(Guid id)
{
var command = new DeleteMember.Command(id);
await _mediator.Send(command).ConfigureAwait(false);
}
Your DbContext is scoped, meaning that Dependency Injection will return the same DbContext object every time one is asked for within the same HTTP request (in the case of ASP.NET).
That means that you should not be calling Dispose on your DbContext (otherwise that same object can't be used a second time). That seems to be what is happening to you, intentionally or not.
That does mean you should not be using using with it. Are you using using anywhere in your code against your DbContext?
I don't think you showed the line where the Exception is being thrown.
Update:
Try overriding Dispose in your MemberContext class. Something like this:
public override void Dispose() {
base.Dispose();
}
But just set a breakpoint there. When it breaks (if it does) check the stack trace and see what called it.
This can also be caused by having async void instead of async Task within WebAPI in my experience.