Serilog LogContext in Blazor with IIS Site Bindings - c#

I use Serilog in my Blazor Server Side Application, which deploy on IIS using site bindings
And I want to ensure that the logs(unhandled exceptions and my custom log info) on these sites are written to different folders by hostname.
My Serilog Configuration:
public static class HostBuilderExtension
{
public static IHostBuilder AddSerilog(this IHostBuilder hostBuilder)
{
return hostBuilder.UseSerilog((hostingContext, loggerConfiguration) =>
{
var appSettings = hostingContext.Configuration.Get<AppSettings>();
loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.Map("Hostname", "ms-hosting", (hostname, wr) =>
wr.Async(to =>
to.File(appSettings.GeneralLogsPath(hostname), rollingInterval: RollingInterval.Day, shared: true)));
});
}
}
GeneralLogsPath
public string GeneralLogsPath(string hostname) => Path.Combine(AppLogsRoot, hostname, "General", "log.log");
Registration in Program.cs:
builder.Host.AddSerilog();
And my custom Middleware to push current hostname to LogContext:
using Serilog.Context;
using System.Collections.Generic;
namespace Herbst.Acc2.CustomerWebUI.Classes;
public class ScopedLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ScopedLoggingMiddleware> _logger;
public ScopedLoggingMiddleware(RequestDelegate next, ILogger<ScopedLoggingMiddleware> logger)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Invoke(HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var hostname = context.Request.Host.Host;
try
{
using (LogContext.PushProperty("Hostname", hostname))
{
await _next(context);
}
}
//To make sure that we don't loose the scope in case of an unexpected error
catch (Exception ex) when (LogOnUnexpectedError(ex))
{
return;
}
}
private bool LogOnUnexpectedError(Exception ex)
{
_logger.LogError(ex, "An unexpected exception occured!");
return true;
}
}
public static class ScopedLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseScopedLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ScopedLoggingMiddleware>();
}
}
In Program.cs
app.UseScopedLogging();
Can I be sure that the message from test-t1.com will never written to \logs\test-t2.com?

Here’s how logical call context works with asynchronous code.
Logical call context data flows with ExecutionContext. This means that it’s not affected by ConfigureAwait(continueOnCapturedContext: false); you can’t “opt-out” of the logical call context. So the logical call context at the beginning of an async method will always flow through to its continuations.
When an async method starts, it notifies its logical call context to activate copy-on-write behavior. This means the current logical call context is not actually changed, but it is marked so that if your code does call CallContext.LogicalSetData, the logical call context data is copied into a new current logical call context before it is changed. Note: the copy-on-write behavior of logical call contexts is only available on .NET 4.5.
Read more about AsyncLocal here
Pay attention to these:
So the logical call context(AsyncLocal) at the beginning of an async method will always flow through to its continuations(In your example this is all the middleware following the current one).
Therefore, in your case, each request will have its own logical call context, so you can be sure that the message from test-t1.com will never written to \logs\test-t2.com?

Related

Executing async code after startup, once API is up for requests

I'm trying to execute logic on start up of my web API and this code will also be async.
I've thought of IStartupFilter but this is sync, which is a problem. StartUp.Configure() is also not an option, sync.
I've thought of IHostedService but this runs before the application is done loading and up for requests.
Any other ways of doing this ?
Something you probably didn't realize is that running the application has 2 parts, that are hidden by default when you call IHost.Run/IHost.RunAsync.
Basically, this:
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
Is equivalent to this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await host.WaitForShutdownAsync();
When StartAsync returns, the application has already started and is ready to consume requests, so you probably want to do this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await PerformSomeWorkAfterStartupAsync();
await host.WaitForShutdownAsync();
Implement IStartupTask, register StartupTaskRunner and YourStartupTask in DI:
services
.AddStartupTasksRunner()
.AddStartupTask<YourStartupTask>();
And here the code based on Andrew's Lock post:
public interface IAsyncStartupTask
{
Task OnStartupAsync(CancellationToken cancellationToken);
}
internal class StartupTasksRunner : IHostedService
{
private readonly IEnumerable<IAsyncStartupTask> _startupTasks;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly SemaphoreSlim _semaphore;
public StartupTasksRunner(IEnumerable<IAsyncStartupTask> startupTasks, IHostApplicationLifetime applicationLifetime)
{
_startupTasks = startupTasks;
_applicationLifetime = applicationLifetime;
_semaphore = new SemaphoreSlim(0);
applicationLifetime.ApplicationStarted.Register(() => _semaphore.Release());
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// wait for ApplicationStarted event to execute when app is listening to web requests
// await _semaphore.WaitAsync(cancellationToken);
foreach (var task in _startupTasks)
{
try
{
await task.OnStartupAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// stop the application if failing startup task if fatal
//_applicationLifetime.StopApplication();
throw;
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStartupTasksRunner(this IServiceCollection services)
{
return services.AddHostedService<StartupTasksRunner>();
}
public static IServiceCollection AddStartupTask<TStartupTask>(this IServiceCollection services)
where TStartupTask : class, IAsyncStartupTask
{
return services.AddSingleton<IAsyncStartupTask, TStartupTask>();
}
}
Notes
The app startup order is:
Start HostedService's
Start Kesterl Server
Fire ApplicationStarted event
If you want to run your startup task before HostedServices started you need to make sure StartupTasksRunner registered before any other HostedService (they are started in registration order).
If you want to run your startup task after all HostedServices started but before application starts to receive web requests make sure you are registering StartupTasksRunner after any other HostedService.
If you want to run your startup task after Kestrel Server is started uncomment // await _semaphore.WaitAsync(cancellationToken); line. Be aware that it may run concurrently with web requests handling.
Also be aware that exceptions thrown by IHostedService.StartAsync method will be swallowed by framework. So if successful execution of startup tasks is necessary for your application uncomment // _applicationLifetime.StopApplication();

Asp.net Core: Can not access a disposed object (different errors)

I know that this problem is here a lot, but I have to say I read everything I could found for like two days and don't get my error.
I created a ASP.net Core REST API and get always different errors:
"Can not access a disposed object.."
"An exception occurred while iterating over the results of a query
for context type.."
"A second operation started on this context before a previous
operation completed"..
Maybe someone of you sees my error or can explain to me, what I'm doing wrong.
Rest-API:
// POST api/events
[HttpPost("create")]
public async Task<IActionResult> CreateAsync([FromBody] EventDTO eventDTO)
{
var newEvent = _mapper.Map<Event>(eventDTO);
try
{
await _eventService.CreateEventAsync(newEvent);
return Ok(newEvent);
}
catch (AppException ex)
{
return BadRequest(new { message = ex.Message });
}
}
Interface:
public interface IEventService
{
Task<IEnumerable<Event>> GetAllEventsAsync();
Task<Event> GetEventByIDAsync(int id);
Task<IEnumerable<Event>> GetEventByCityAsync(string city);
Task<Event> CreateEventAsync(Event newEvent);
void UpdateEventAsync(Event newEvent, Event existing, int eventId);
void DeleteEventAsync(Event existing);
}
Eventservice:
public class EventService : IEventService
{
private MeMeContext _dbContext;
public EventService(MeMeContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Event> CreateEventAsync(Event newEvent)
{
_dbContext.Events.Add(newEvent);
await _dbContext.SaveChangesAsync();
return newEvent;
}
...
}
Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc().
SetCompatibilityVersion(CompatibilityVersion.Version_2_2).
AddJsonOptions(opts => opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
services.AddDbContext<MeMeContext>(opts => opts.UseNpgsql(Configuration.GetConnectionString(DATABASE)));
services.AddScoped<MeMeContext>();
// configure DI for application services
services.AddScoped<IUserService, UserService>();
services.AddScoped<IEventService, EventService>();
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new AutoMapperProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
...
}
One thing that I don't understand also, is that I get different errors, when I start my application with Visual Studio or with "dotnet run". One thing that also happens from time to time is, that sometimes my code works, when I do other things on the REST API.
When you need more information, just ask. I'm happy with every hint that you can give me :)
Thanks in advance!
You're not awaiting an async method. As such, the code in the action moves on while that CreateEventAsync logic is running. When the response returns, the context goes away, since its lifetime is that scope.
In other words, you have essentially a race condition. If the CreateEventAsync logic happens to finish before the response returns, everything is fine. However, if it takes longer than returning the response, then the context is gone (along with your other scoped services), and you start throwing exceptions.
Long and short, use the await keyword:
await _eventService.CreateEventAsync(newEvent);
Async is not the same as running something in the background. If you want the action to be able to return before this logic completes, then you should schedule this to run on a background service instead. See: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio

Error in Seeding IdentityRole in Asp.net core

I am having trouble seeding data into the identity role table. I always get the error
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
<>4__this._roleManager was null
I am not sure why this is happening and why it's not seeding data into the table.How do I fix this?
Below is my code
public class UserRoleSeed
{
private readonly RoleManager<IdentityRole> _roleManager;
private string[] _roleArray = { "Admin, TerminalManager, Dispatcher, Driver, Mechanic, Recruiter, MechanicManger" };
public UserRoleSeed(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}
public async void Seed()
{
foreach (string index in _roleArray)
{
if ((await _roleManager.FindByNameAsync(index)) == null)
{
await _roleManager.CreateAsync(new IdentityRole { Name = index });
}
}
}
}
for my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TransportDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddEntityFrameworkStores<TransportDbContext>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(
routes =>
{
routes.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
});
}
// seeds data into the identity role table
new UserRoleSeed(app.ApplicationServices.GetService<RoleManager<IdentityRole>>()).Seed();
}
}
}
You're using an async method to seed your roles, but you're not awaiting it. That means that your code keeps moving on, eventually taking variables you're depending on in your async method along with it when branches go out of scope. Hence, NullReferenceExceptions.
Additionally, services like RoleManager<TRole> are "scoped" services, meaning they can only be retrieved from a particular active scope. In an actual request, a scope would be created for the request, allowing these services to be injected into anything within the request pipeline. However, here, you have no active scope, and therefore must create one.
Instead of attempting to seed as part of your Configure method, you should move this code out into your Program class. The code below addresses both of the above concerns:
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
await new UserRoleSeed(scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>()).Seed();
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Essentially you'll use an async Main to run your app, which then gives you the ability to await additional things like your seed. For what it's worth, this can be shortened somewhat in C# 7.2 with an actual async Main, i.e.:
public static async Task Main(string[] args)
Without having to proxy from Main to a MainAsync, but under the hood the compiler just sets up this same construction for you.
That's the shortest path to get this code working, but you still have a couple of minor issues. First, you should avoid using async void, which is an antipattern. You're essentially swallowing the async output with that, including any exceptions that may be thrown. You should virtually always use async Task as the return when you don't care about the actual return. The few situations where async void is appropriate are known to individuals who need to use it. In other words, if you don't know when you should use async void, then you shouldn't be using async void.
Also, while there's nothing technically wrong with newing up a class and passing the dependency into the constructor, it's more appropriate in this case to make the class static and pass the required dependencies into the seed method:
await UserRoleSeed.Seed(roleManager);
Finally, again, while not critical, it's convention to name async methods with an Async suffix. This makes it clear that the method is async and prevents accidentally not awaiting the method simply because it's not obvious that it needs to be awaited (which may have been the case here). In short, change the name from Seed to SeedAsync, since it does async work.
Ok guys I figured it out, Below is my solution.
I basically modified the class for seeding the data and renamed it DbInitializer.cs
public class DbInitializer
{
private static readonly string[] _roleArray = { "Admin", "Terminal Manager", "Dispatcher", "Driver", "Mechanic", "Recruiter", "Mechanic Manger" };
public static async Task InitializeAync (TransportDbContext context, IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<Role>>();
foreach (string index in _roleArray)
{
if ((await roleManager.FindByNameAsync(index)) == null)
{
await roleManager.CreateAsync(new Role { Name = index });
}
}
}
}}
then I called the function in my Program.cs file as suggested by #Chris Pratt.
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TransportDbContext>();
await DbInitializer.InitializeAync(context, services);
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
} }
thanks to everyone who tried to help me

How to start HostedService in MVC Core app without http request

In my MVC .NET core 2.2 app there is HostedService which doing background work.
It is register in ConfigureServices method of Startap class
services.AddHostedService<Engines.KontolerTimer>();
Since this is background service independent of users requests I want to start my background service immediately when app starts.
Now is case to my HostedService staring after first user request.
What is proper way to start HostedService when MVC Core app start
My serivce looks like this one https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Looks like I have problem staring app at all.
My porgram cs looks like
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog((ctx, config) => { config.ReadFrom.Configuration(ctx.Configuration); })
.UseStartup<Startup>();
}
And I do not hit any break point before first user request.
Am I miss something, this is default .Net Core app created by VS2017
Here is my starup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private Models.Configuration.SerialPortConfiguration serialPortConfiguration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddDbContext<Data.Parking.parkingContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddHostedService<Engines.KontolerTimer>();}
When you run this using Visual Studio, you are likely using IIS Express, which isn't going to run your ASP.NET Core project until the first request is made (that's really just how IIS works by default). This applies when using the InProcess hosting-model that's new with ASP.NET Core 2.2, which I expect you must be using in order to see this issue. See this GitHub issue for more.
You can prove this theory by removing the AspNetCoreHostingModel XML element from the .csproj file that you're using to host the ASP.NET Core application (which will switch it back to the OutOfProcess mode). It looks like there's a "Hosting Model" option under "Debug" in the project properties dialog of VS2017 that you can change to "Out Of Process" if you don't want to edit the .csproj directly.
If you want the hosting-model to be out-of-process only for a production site, you could use a Web.config transform, for example. If you want it to be out-of-process both during development and in production, just changing the property I called out above will be enough as this gets converted automatically into a Web.config property. If you would prefer to use the in-process model, enabling preload in the IIS application is a good option (described here).
Background services start when your application starts, then it's up to you to synchronize with it.
You can implement a background service by using the BackgroundService class from the namespace Microsoft.Extensions.Hosting(Microsoft.Extensions.Hosting.Abstractions assembly):
First the declare the interface of your service (in this case it is empty, not nice, but clean):
public interface IMyService : IHostedService
{
}
Then, declare your service. The following snippet declares a service that at startup waist for 5 seconds, and then executes a task every 2 minutes and half:
internal sealed class MyService : BackgroundService, IMyService
{
private const int InitialDelay = 5 * 1000; //5 seconds;
private const int Delay = (5 * 60 * 1000) / 2; // 2.5 minutes
private readonly ILogger<MyService> m_Logger;
public MyService(ILogger<MyService> logger, IServiceProvider serviceProvider)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
this.m_Logger = logger;
this.m_ServiceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
m_Logger.LogDebug($"MyService is starting.");
stoppingToken.Register(() => m_Logger.LogDebug($"MyService background task is stopping because cancelled."));
if (!stoppingToken.IsCancellationRequested)
{
m_Logger.LogDebug($"MyService is waiting to be scheduled.");
await Task.Delay(InitialDelay, stoppingToken);
}
m_Logger.LogDebug($"MyService is working.");
while (!stoppingToken.IsCancellationRequested)
{
await DoSomethingAsync();
await Task.Delay(Delay);
}
m_Logger.LogDebug($"MyService background task is stopping.");
}
catch (Exception ex)
{
m_Logger.LogDebug("MyService encountered a fatal error while w task is stopping: {Exception}.", ex.ToString());
}
}
private async Task DoSomethingAsync()
{
// do something here
await Task.Delay(1000);
}
}
As you can see, it's up to you to keep the background service "alive". Finally, you have to register it in your Startup.cs at the end of your ConfigureServices method:
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, MyService>();
This is sufficient to have the service started. keep in mind that your application could be actually started at a later time if hosted in IIS: your application is (re)started everytime your assembly is recycled. Instead, using Kestrel, provides a single instance application which will not be recycled.
For those using .Net Core 2.1 or lower, the Background class is not available, but you can get the definition from github (I post what I used in the past as the github repository can be moved):
//borrowed from .NET Core 2.1 (we are currently targeting 2.0.3)
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
For me... background tasks weren't starting until the first page request.
But then I noticed in my Publish / Edit, I didn't have Destination Url set. (and also I didn't have a home Index page)...
Once I added a valid Destination Url... that page would popup after publishing and be my "first" page request and background tasks would start.
If you want o have a Service doing background tasks (similar to old Windows Services) I would suggest you to use: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 instead of a WebHost.
WebHost add a lot of stuff that probably you won't need since seems a simple background job (assuming that reading your code).
Hosted services do start when the host is starting. With the WebHost, the hosted services will be started right after the application has started. This means that if implemented correctly, your hosted service will run without requiring a request to come in.
When I try your example hosted service on a fresh ASP.NET Core application, it works just fine, so if it is not working for you, then apparently your actual implementation KontolerTimer is not correct.

Cannot access a disposed object in ASP.NET Core when injecting DbContext

On an ASP.NET Core project I have the following on Startup:
services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));
services.AddTransient<IValidationService, ValidationService>();
services.AddTransient<IValidator<Model>, ModelValidator>();
The ValidationService is as follows:
public interface IValidationService {
Task<List<Error>> ValidateAsync<T>(T model);
}
public class ValidationService : IValidationService {
private readonly IServiceProvider _provider;
public ValidationService(IServiceProvider provider) {
_provider = provider;
}
public async Task<List<Error>> ValidateAsync<T>(T model) {
IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();
return await validator.ValidateAsync(model);
}
}
And the ModelValidator is as follows:
public class ModelValidator : AbstractValidator<Model> {
public ModelValidator(Context context) {
// Some code using context
}
}
When I inject a IValidationService in a controller and use it as:
List<Error> errors = await _validator.ValidateAsync(order);
I get the error:
System.ObjectDisposedException: 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 is 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: 'Context'.
Any idea why I am having this error when using Context inside ModelValidator.
How to fix this?
UPDATE
So I changed the code to:
services.AddScoped<IValidationService, ValidationService>();
services.AddScoped<IValidator<Model>, ModelValidator>();
But I get the same error ...
UPDATE - Seed Data Code inside Configure method on Startup
So on Configure method I have:
if (hostingEnvironment.IsDevelopment())
applicationBuilder.SeedData();
And the SeedData extension is:
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static void SeedData(this IApplicationBuilder builder) {
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
What am I missing?
UPDATE - A possible solution
Changing my Seed method to the following seems to work:
using (IServiceScope scope =
_provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
Context context = _provider.GetService<Context>();
// Insert data in database
}
Just a guess in what causes your error:
You are using DI and async calls. If, somewhere in your call stack, you return a void instead of Task, you get the described behavior. At that point, the call is ended and the context disposed. So check if you have an async call that returns a void instead of Task. If you change the return value, the ObjectDisposedException is probably fixed.
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static async Task SeedData(this IApplicationBuilder builder) { //This line of code
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
And in configure:
if (hostingEnvironment.IsDevelopment()){
await applicationBuilder.SeedData();
}
Blog post on how to fix this error: Cannot access a disposed object in ASP.NET Core when injecting DbContext
I had a similar issue working with asp.net core. I have an async POST method in my controller and when it returns void I will have this exception. After I changed the POST method return a TASK the problem was solved.
Change from:
public async void PostAsync([FromBody] Model yourmodel)
To
public async Task PostAsync([FromBody] Model yourmodel)
Update for ASP.NET Core 2.1
In ASP.NET Core 2.1 the methods changed slightly. The general method is similar to the 2.0, just the methods name and return types have been changed.
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.Seed();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return new WebHostBuilder()
...; // Do not call .Build() here
}
Applies for ASP.NET Core 2.0
With ASP.NET Core 2.0 there have been some changes in how EF Core tools (dotnet ef migrations etc.) determine the DbContext and connection string at design time.
The below answer leads that the migrations and seeding are applied when calling any of the dotnet ef xxx commands.
The new pattern for getting a design time instance for the EF Core tools is by using an BuildHostWeb static method.
As per this announcement, EF Core will now use the static BuildWebHost method which configures the whole application, but doesn't run it.
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
// Tools will use this to get application services
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
Replace this in your old Main method
public static void Main(string[] args)
{
var host = BuildWebHost(args)
.Seed();
host.Run();
}
Where Seed is an extension method:
public static IWebHost Seed(this IWebHost webhost)
{
using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
{
// alternatively resolve UserManager instead and pass that if only think you want to seed are the users
using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
}
}
}
public static class SeedData
{
public static async Task SeedAsync(ApplicationDbContext dbContext)
{
dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
}
}
Old Answer, still applies to ASP.NET Core 1.x
There is a semi-official pattern on how to seed Entity Framework Core in ASP.NET Core application you should apply, because during application startup there is no Request and hence no RequestServices (which resolves scoped services).
In essence it boils down to creating a new scope, resolve the types you need and dispose the scope again once you're finished.
// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var db = serviceScope.ServiceProvider.GetService<AppDbContext>();
if (await db.Database.EnsureCreatedAsync())
{
await SeedDatabase(db);
}
}
One of the reasons directly resolving a service via app.ApplicationServices.GetService<MyService>() is that ApplicationServices is the application (or lifetime) scope provider and the services resolved here stay alive until the application is shut down.
Usually the scoped container will resolve from it's parent container, if the object already exists there. So if you instantiate the DbContext this way in the application, it will be available in ApplicationServices container and when a request happens, a child container will be created.
Now when resolving the DbContext it won't be resolved as scoped, because it already exists in the parent container, so the instance of the parent container will be returned instead. But since it has been disposed during the seeding, it won't be accessible.
A scope container is nothing else then a singleton container with limited lifetime.
So never resolve scoped services in Application startup w/o using the pattern above of first creating a scope and resolving from it.
If you are using any async void please replace it with async Task
Had the same issue. Hope this helps someone. In addition to making the method async and return a Task, you need to make sure that the method will also be awaited wherever you are calling it.
the problem is that DBContext is scoped per request by default, but you have things that depend on it scoped as transient, so they do not have the same scope and DBContext may be disposed before you are done using it
Similar to Yang Zhang, I had to change my controller function
From:
public IActionResult MyFunc([FromBody]string apiKey)
To:
public async Task<IActionResult> MyFunc([FromBody]string apiKey)
I'd like to share my solution for those who are trying to start a background task in their controllers. That means you want to start a task and don't want to wait for the result like audit logging to database. If you are creating a task and try to do database operations in that task you will receive this 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.\r\nObject name: 'DBContext'.
Already explained in details. Find it here
In my case, it wasn't an Async problem, but the code had a
using (DataContext dc=dataContext) {}
block, and of course, the context was disposed after that.
In my case the controller method was async and it was returning a task but inside that I had 2 await calls. First await calls gets some data from a service and second await call writes to the DB using EF. I had to remove the await from this second call and only then it worked. I didn't remove async/await from method signatures. I just called the second method without await.
I was facing a similar error and later was able to resolve it.
I was calling the async method without using await.
old code
var newUser = _repo.Register(newUserToCreate);
with the fix made
var newUser = await _repo.Register(newUserToCreate);

Categories

Resources