I trying to inject the ApplicationDbcontext into iLogger.
When I use _context variable inside the CustomLoggerProvider or CustomLoggerExtension it'is work.
While when the software CreateLogger create a instance of CustomLogger, I have a problem with ApplicationDbContext, when I use the _context variable to access the database the application crashes and doesn't work.
The following there is the error log:
System.ObjectDisposedException HResult=0x80131622 Messaggio=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.
Origine=Microsoft.EntityFrameworkCore Analisi dello stack: at
Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at
Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.Add(TEntity
entity) at Application.Services.EventLogDB.saveInDb(Cliente c) 28
at Application.Models.DbLogger.CustomLogger.Log[TState](LogLevel
logLevel, EventId eventId, TState state, Exception exception, Func3
formatter) in C:\Users......\Models\DbLogger\CustomLogger.cs:line 122
at
Microsoft.Extensions.Logging.Logger.g__LoggerLog|12_0[TState](LogLevel
logLevel, EventId eventId, ILogger logger, Exception exception, Func3
formatter, List1& exceptions, TState& state)
I think it's a problem of the service life cycle.
Is there anyone who knows a solution?
Startup.cs :
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.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
var serviceHttp = serviceProvider.GetService<IHttpContextAccessor>();
var serviceDbContext = serviceProvider.GetService<GestionaleOfficinaContext>();
// THIS IS METHOD THAT SHOWS THE BAD BEHAVIOUR
// Inside logger I need of Http context service and applicationDbContext
loggerFactory.AddCustomLogger(serviceHttp, serviceDbContext);
}
}
CustomLogger.cs :
public static class CustomLoggerExtensions
{
public static ILoggerFactory AddCustomLogger(this ILoggerFactory factory, IHttpContextAccessor accessor, ApplicationDbContext_context,
Func<string, LogLevel, bool> filter = null)
{
factory.AddProvider(new CustomLogProvider(filter, accessor, _context));
return factory;
}
}
public class CustomLogProvider : ILoggerProvider
{
private readonly Func<string, LogLevel, bool> _filter;
private readonly IHttpContextAccessor _accessor;
private readonly ApplicationDbContext_context;
public CustomLogProvider(Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor, ApplicationDbContext context)
{
_filter = filter;
_accessor = accessor;
_context = context;
// IF I USE THE variable _context in this place of code the applicationDbContext is available
// and so the behaviour is right
//if (_context != null)
//{
// Cliente cl = new Cliente();
// cl.codiceFiscale = "ALFA";
//
// _context.Add(cl);
// _context.SaveChanges();
//}
}
public ILogger CreateLogger(string categoryName)
{
// In this part of code there is the strange behaviour
// _context is different by null, but when I use _context
// the lifetime of service ApplicationDbContext is END
if (_context != null)
{
Cliente cl = new Cliente();
cl.codiceFiscale = "CCC";
_context.Add(cl);
_context.SaveChanges();
}
return new CustomLogger(categoryName, _filter, _accessor, _context);
}
public void Dispose()
{
//base.Dispose();
}
}
// THE ERROR IS IN THIS CLASS
public class CustomLogger : ILogger
{
private string _categoryName;
private Func<string, LogLevel, bool> _filter;
private readonly IHttpContextAccessor _accessor;
private readonly GestionaleOfficinaContext _context;
public CustomLogger(string categoryName, Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor, GestionaleOfficinaContext context)
{
_categoryName = categoryName;
_filter = filter;
_accessor = accessor;
_context = context;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return (_filter == null || _filter(_categoryName, logLevel));
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
var message = formatter(state, exception);
if (string.IsNullOrEmpty(message))
{
return;
}
message = $"{ logLevel }: {message}";
// your implementation
}
}
Related
I want to get the list of Module R by using static method.
I have tried many other answer but my problem not solved
.Net Core 6
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _DbContext;
private readonly IWebHostEnvironment _environment;
public ModuleR ModuleR { get; set; }
public UnitOfWork(ApplicationDbContext context, IWebHostEnvironment environment)
{
_DbContext = context;
_environment = environment;
ModuleR = new ModuleR(_DbContext);
}
public async Task Commit()
{
await _DbContext.SaveChangesAsync();
}
public static IEnumerable<Module> GetModules()
{
return ModuleR.GetAllList();
// Through error
// An object reference is required for the non-static field , method or property UnitOfWork.ModuleR
}
}
public class ModuleR : GenericR<Module>, IModule
{
private readonly ApplicationDbContext _DbContext;
public ModuleR(ApplicationDbContext context) : base(context)
{
_DbContext = context;
}
public IEnumerable<Module> GetAllModuleByName(string Name)
{
throw new NotImplementedException();
}
}
public interface IModule
{
IEnumerable<Module> GetAllModuleByName(string Name);
}
public interface IUnitOfWork : IDisposable
{
Task Commit();
}
// Programe.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using PMS.Contracts;
using PMS.Data;
using PMS.Implementation;
//ExpandoObject object.net core
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
builder.Services.AddScoped(typeof(IRepository<>), typeof(GenericR<>));
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<ApplicationDbContext>();
//https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-7.0#add-role-services-to-identity
//builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<ApplicationDbContext>().AddRoles<IdentityRole>();
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
Try : Is there a way to call a non-static method from a static method?
I want to get the list ModuleR with static method.
if i create new constructor then ModuleR Remains Null
I'm logging exceptions to database in asp.net core. MyDbContext take HttpContextAccessor parameter.So, I'm sending HttpContextAccessor to MyDbContext.cs for access my JWT. But, I can't access my HttpContextAccessor from Startup.cs. How can I achieve this?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<MyDbContext>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
context.Response.AddApplicationError(error,???????);//I want access HttpContextAccessor
await context.Response.WriteAsync(error.Error.Message);
}));
app.UseHttpsRedirection();
app.UseMvc();
}
ExceptionHelper.cs
public static class ExceptionHelper
{
public static async Task AddApplicationError(this HttpResponse response, IExceptionHandlerFeature error, IHttpContextAccessor httpContextAccessor)
{
Log log = new Log();
log.Message = error.Error.Message;
MyDbContext context = new MyDbContext(null, httpContextAccessor);
UnitOfWork uow = new UnitOfWork(context);
uow.LogRepo.AddOrUpdate(log);
await uow.CompleteAsync(false);
}
}
MyDbContext
public class MyDbContext : DbContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(GetOptions())
{
_httpContextAccessor = httpContextAccessor;
}
private static DbContextOptions GetOptions()
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), "server=asd; database=; user id=asd; password=1234").Options;
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var token = _httpContextAccessor.HttpContext.Request.Headers["Authorization"];
var audits = AuditHelper.AddAuditLog(base.ChangeTracker, token);
return (await base.SaveChangesAsync(true, cancellationToken));
}
}
You can inject whatever you need into the Configure method. You have already added it to the service collection with this line:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So all you need to do is add it to the list of arguments on the method like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor accessor)
{
// make use of it here
}
As an aside: I would also point out that it's a bit of a code smell that you are manually creating an instance of your DbContext inside your static helper class when you are using dependency injection.
Update in response to comment
In order to tidy things up a bit I would start by changing your startup to configure you DbContext something like this:
public class Startup
{
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// register other things here...
services.AddDbContext<DataContext>(o => o.UseSqlServer(
config.GetConnectionString("MyConnectionString") // from appsettings.json
));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// set up app here...
}
}
You can then remove the .GetOptions() method from MyDbContext, and change the constructor to:
public MyDbContext(DbContextOptions<MyDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
Then you inject an instance of MyDbContext into whatever class needs access to it. The problem is that (to my knowledge) DI does not work well with static classes/methods, and you are using an extension method on the HttpResponse to log your error.
In my opinion it would be better to create a class that is responsible for logging the error with a dependency on your MyDbContext and have that injected into the Configure method:
public class ErrorLogger
{
private MyDataContext db;
public ErrorLogger(MyDataContext db) => this.db = db;
public void LogError(IExceptionHandlerFeature error)
{
Log log = new Log();
log.Message = error.Error.Message;
UnitOfWork uow = new UnitOfWork(this.db);
uow.LogRepo.AddOrUpdate(log);
await uow.CompleteAsync(false);
}
}
Register it with the DI container as you have with other things, then inject it into Configure instead of the HTTP accessor:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ErrorLogger logger)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
logger.LogError(error);
await context.Response.WriteAsync(error.Error.Message);
}));
}
I have not tested this, and I am not familiar with .UseExceptionHandler(...) as I use application insights to log exceptions etc (take a look at it if you've not seen it). One thing to be aware of is the scope of your dependencies; your DbContext will be Scoped by default (and I think you should leave it that way), which means you cannot inject it into Singleton objects.
I need to inject a db context object into a custom middleware called AuthenticateClient, but I get the exception:
InvalidOperationException: Cannot resolve scoped service
'LC.Tools.API.Data.ApiDbContext' from root provider.
AuthenticateClient.cs:
public class AuthenticateClient
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly GenericUnitOfWork _worker;
public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
{
_next = next;
_logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
_worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("key") || !context.Request.Headers.Keys.Contains("pass"))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Key or Pass missing from request header values");
return;
}
else
{
Client client;
string key, pass;
key = context.Request.Headers["key"];
pass = context.Request.Headers["pass"];
client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));
if (client == null)
{
_logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host });
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Authentication failed");
return;
}
}
await _next.Invoke(context);
}
}
AuthenticateClientExtension.cs:
public static class AuthenticateClientExtension
{
public static IApplicationBuilder UseClientAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuthenticateClient>();
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApiDbContext>(options => options.UseSqlServer(this.ConnectionString));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Settings> lclog)
{
loggerFactory.AddLCLog(lclog.Value);
app.UseClientAuthentication();
app.UseMvc();
}
ApiDbContext.cs:
public class ApiDbContext : DbContext, IApiDbContext
{
public ApiDbContext(DbContextOptions<ApiDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
...
}
}
The solution was to use IApplicationBuilder to create scope and init
ApiDbContext and pass it to the middleware object. I also changed how I register the ApiDbContext
AuthenticateClientExtension.cs:
public static class AuthenticateClientExtension
{
public static IApplicationBuilder UseClientAuthentication(this IApplicationBuilder builder)
{
var scope = builder.ApplicationServices.CreateScope();
ApiDbContext db = scope.ServiceProvider.GetRequiredService<ApiDbContext>();
return builder.UseMiddleware<AuthenticateClient>(db);
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApiDbContext>(options => options.UseSqlServer(this.ConnectionString));
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Settings> lclog)
{
...
app.UseClientAuthentication();
app.UseMvc();
}
I am attempting to implement the API Key Validator mentioned in this post. I am running into an issue where the injected service I am using to do validation in the middleware class is returning:
InvalidOperationException: Cannot resolve 'FoosballKeepr.Services.Interfaces.ILeagueService' from root provider because it requires scoped service 'FoosballKeepr.Data.FoosballKeeprContext'.
I believe I am registering my dbContext, services, and repositories correctly in Startup.cs.
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)
{
//MVC
services.AddMvc();
//Database
var connection = #"Server=localhost\SQLEXPRESS;Database=FoosballKeepr;Trusted_Connection=True;";
services.AddDbContext<FoosballKeeprContext>(options => options.UseSqlServer(connection));
//Services
services.AddTransient<IPlayerService, PlayerService>();
services.AddTransient<ILeagueService, LeagueService>();
//Repositories
services.AddTransient<IPlayerRepository, PlayerRepository>();
services.AddTransient<ILeagueRepository, LeagueRepository>();
}
// 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();
}
app.UseMiddleware<ApiKeyValidatorMiddleware>();
app.UseMvc();
}
}
Custom middleware validator:
public class ApiKeyValidatorMiddleware
{
private readonly RequestDelegate _next;
private ILeagueService _leagueService;
public ApiKeyValidatorMiddleware(RequestDelegate next, ILeagueService leagueService)
{
_next = next;
_leagueService = leagueService;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("x-api-key"))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("API Key Missing.");
return;
}
else
{
int leagueId = _leagueService.ValidateApiKey(context.Request.Headers["x-api-key"]);
if (leagueId == 0)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid API Key");
return;
}
else
{
context.Items["LeagueId"] = leagueId;
}
}
await _next.Invoke(context);
}
}
Service
public class LeagueService : ILeagueService
{
private readonly ILeagueRepository _leagueRepository;
public LeagueService(ILeagueRepository leagueRepository)
{
_leagueRepository = leagueRepository;
}
public int ValidateApiKey(string apiKey)
{
return _leagueRepository.ValidateApiKey(apiKey);
}
}
Repository
public class LeagueRepository : ILeagueRepository
{
private readonly FoosballKeeprContext _context;
public LeagueRepository(FoosballKeeprContext context)
{
_context = context;
}
public int ValidateApiKey(string apiKey)
{
var query = from l in _context.League
where l.ApiKey == apiKey
select l.LeagueId;
return query.FirstOrDefault();
}
}
This is my first time implementing custom middleware functionality so I feel like my issue is not correctly setting something up in the correct context, but nothing is popping up as obvious. Does this look familiar to anyone??
The problem is that middlewares don't have a scope, given that:
Middleware is constructed once per application lifetime
So, when you need to inject scoped services, you do it at the Invoke operation (what's known as method injection):
public async Task Invoke(HttpContext context, ILeagueService service)
{
//...
}
i'm trying to add tenant name to every log done by asp.net core.
Im using Sasskit for multi-tenant.
And im getting some scope problems. I would like some feedback on how i can get this to work:
This is my custom logger:
public class MultiTenantLogger : IMultiTenantProvider
{
private AppTenant _tenant;
public MultiTenantLogger(AppTenant tenant)
{
_tenant = tenant;
}
public string GetTenantName<T>()
{
var typeDisplayName = GetTypeDisplayName(typeof(T));
if (_tenant == null)
{
return typeDisplayName;
}
return $"Tenant: {_tenant.Name}";
}
}
.
public class MultiTenantLogger<T> : ILogger<T>
{
private readonly ILogger _logger;
public MultiTenantLogger(ILoggerFactory factory, IMultiTenantProvider multiTenantProvider)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (multiTenantProvider == null)
{
throw new ArgumentNullException(nameof(multiTenantProvider));
}
var category = multiTenantProvider.GetTenantName<T>();
_logger = factory.CreateLogger(category);
}
IDisposable ILogger.BeginScope<TState>(TState state) => _logger.BeginScope(state);
bool ILogger.IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
void ILogger.Log<TState>(LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
=> _logger.Log(logLevel, eventId, state, exception, formatter);
}
This is my startup:
public void ConfigureServices(IServiceCollection services)
{
_services = services;
services.AddMultitenancy<AppTenant, CachingAppTenantResolver>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
services.AddMvc();
services.AddTransient<IMultiTenantProvider, MultiTenantLogger>();
services.Replace(ServiceDescriptor.Transient(typeof(ILogger<>), typeof(MultiTenantLogger<>)));
}
I get this error:
Cannot consume scoped service 'AspNetMvcSampleModels.AppTenant'
from singleton 'Microsoft.AspNetCore.Hosting.IApplicationLifetime'.
Any feedback on how to get this working is welcome!
i think you need to make both Singleton or Scoped
you can try to replace
services.AddTransient<IMultiTenantProvider, MultiTenantLogger>();
with
services.AddSingleton<IMultiTenantProvider, MultiTenantLogger>();