MultiTenant custom ILogger - cannot consume scoped service from singleton - c#

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>();

Related

How do I get an registered instance from services without injecting the service in .netcore

I have a service registered in my Startup.cs. Instead of creating a new instance of SomeService, how can I get the registered instance of SomeService without injecting it into my TestService?
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISomeService, SomeService>();
}
public class TestService()
{
public void DoSomething()
{
var SomeService service = new SomeService();
}
}
You would still need access to the service container, so you would need to inject IServiceProvider or provide it somehow, you can then just call GetService<T> on the provider and retrieve the instance (or any other) manually.
For example:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISomeService, SomeService>();
}
public class TestService()
{
private readonly IServiceProvider _provider;
public TestService(IServiceProvider provider)
{
_provider = provider;
}
public void DoSomething()
{
var service = _provider.GetService<ISomeService>();
}
}
If you are doing this as part of testing you can build your own container, for example:
var serviceProvider = new ServiceCollection()
.AddSingleton<ISomeService, SomeService>()
.BuildServiceProvider();

How to inject the dbContext into iLogger

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
}
}

TestStartup.cs not working as Startup.cs in .NET Core functional tests

Im working on functional testing with my WebApi project.
In my Startup.cs i have registered service, Mediatr:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
options.Filters.Add(typeof(CustomExceptionFilterAttribute)))
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMediatR(new CreateUserCommand().GetType().Assembly);
//and other services
}
}
This Startup is default from my Api project.
To functional testing my app, i made TestStartup.cs class:
public class TestStartup
{
public TestStartup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
options.Filters.Add(typeof(CustomExceptionFilterAttribute)))
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMediatR(typeof(ConfirmReservationCommand.Handler).GetTypeInfo().Assembly);
//and ofc, other services
}
}
My question is, why when i pass TestStartup.cs (that exisiting in Tests.csproj, not in Api.csproj as Startup.cs) Mediatr cant be registered properly (I get Internal Server Error 500), but when i pass Startup.cs into my fixture, everything works ok?
my Test:
public class ReservationControllerTest : IClassFixture<CustomWebApplicationFactory<TestStartup>>
{
private readonly HttpClient _client;
public ReservationControllerTest(CustomWebApplicationFactory<TestStartup> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Server_Should_Return_Reservation_List()
{
var response = await _client.GetAsync("/api/Reservation");
string json = await response.Content.ReadAsStringAsync();
var reservations = json.DeserializeObjectFromJson<ReservationsListViewModel>();
// when i pass Startup: Code 200OK, When i pass TestStartup: Error 500
response.EnsureSuccessStatusCode();
reservations.ShouldBeOfType<ReservationsListViewModel>();
reservations.Reservations.ShouldNotBeEmpty();
}
}
My CustomWebApplicationFactory Looks like this:
ublic class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddDbContext<IDbContext, DbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
options.UseInternalServiceProvider(serviceProvider);
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
//getting context and data seeding
}
});
base.ConfigureWebHost(builder);
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder()
.UseStartup<TStartup>();
}
Thanks for any advice
EDIT:
Exception message and strack trace:
stackTrace: " at MediatR.Internal.RequestHandlerBase.GetHandler[THandler](ServiceFactory factory)
at MediatR.Internal.RequestHandlerWrapperImpl`2.<>c__DisplayClass0_0.<Handle>g__Handler|0()
at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at SSPark.Api.Controllers.ReservationController.GetAllReservations() in C:\Users\MichaƂ\source\repos\ss\Controllers\ReservationController.cs:line 25
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at System.Threading.Tasks.ValueTask`1.get_Result()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()"
message: "Error constructing handler for request of type MediatR.IRequestHandler`2[SSPark.Application.Reservation.Query.GetAllReservationsQuery,SSPark.Application.Reservation.Query.ReservationsListViewModel]. Register your handlers with the container. See the samples in GitHub for examples."
The type param for WebApplicationFactory is TEntryPoint. It's used to indicate the assembly of your application, not which "Startup" class is utilized. In other words, it could just as easily be WebApplicationFactory<Program> or even WebApplicationFactory<Foo>, where Foo is simply a random class in your application. The actual "Startup" class is specified via UseStartup.
Using Startup is the conventional approach, though, so you should keep it as WebApplicationFactory<Startup>, where Startup is a reference to the SUT's Startup class, and then when setting up the factory you do:
WebHost.CreateDefaultBuilder().UseStartup<TestStartup>();

Cannot resolve scoped service from root provider .Net Core 2

When I try to run my app I get the error
InvalidOperationException: Cannot resolve 'API.Domain.Data.Repositories.IEmailRepository' from root provider because it requires scoped service 'API.Domain.Data.EmailRouterContext'.
What's odd is that this EmailRepository and interface is set up exactly the same as far as I can tell as all of my other repositories yet no error is thrown for them. The error only occurs if I try to use the app.UseEmailingExceptionHandling(); line. Here's some of my Startup.cs file.
public class Startup
{
public IConfiguration Configuration { get; protected set; }
private APIEnvironment _environment { get; set; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_environment = APIEnvironment.Development;
if (env.IsProduction()) _environment = APIEnvironment.Production;
if (env.IsStaging()) _environment = APIEnvironment.Staging;
}
public void ConfigureServices(IServiceCollection services)
{
var dataConnect = new DataConnect(_environment);
services.AddDbContext<GeneralInfoContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.GeneralInfo)));
services.AddDbContext<EmailRouterContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.EmailRouter)));
services.AddWebEncoders();
services.AddMvc();
services.AddScoped<IGenInfoNoteRepository, GenInfoNoteRepository>();
services.AddScoped<IEventLogRepository, EventLogRepository>();
services.AddScoped<IStateRepository, StateRepository>();
services.AddScoped<IEmailRepository, EmailRepository>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseAuthentication();
app.UseStatusCodePages();
app.UseEmailingExceptionHandling();
app.UseMvcWithDefaultRoute();
}
}
Here is the EmailRepository
public interface IEmailRepository
{
void SendEmail(Email email);
}
public class EmailRepository : IEmailRepository, IDisposable
{
private bool disposed;
private readonly EmailRouterContext edc;
public EmailRepository(EmailRouterContext emailRouterContext)
{
edc = emailRouterContext;
}
public void SendEmail(Email email)
{
edc.EmailMessages.Add(new EmailMessages
{
DateAdded = DateTime.Now,
FromAddress = email.FromAddress,
MailFormat = email.Format,
MessageBody = email.Body,
SubjectLine = email.Subject,
ToAddress = email.ToAddress
});
edc.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
edc.Dispose();
disposed = true;
}
}
}
And finally the exception handling middleware
public class ExceptionHandlingMiddleware
{
private const string ErrorEmailAddress = "errors#ourdomain.com";
private readonly IEmailRepository _emailRepository;
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next, IEmailRepository emailRepository)
{
_next = next;
_emailRepository = emailRepository;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _emailRepository);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception,
IEmailRepository emailRepository)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
var email = new Email
{
Body = exception.Message,
FromAddress = ErrorEmailAddress,
Subject = "API Error",
ToAddress = ErrorEmailAddress
};
emailRepository.SendEmail(email);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int) code;
return context.Response.WriteAsync("An error occured.");
}
}
public static class AppErrorHandlingExtensions
{
public static IApplicationBuilder UseEmailingExceptionHandling(this IApplicationBuilder app)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
return app.UseMiddleware<ExceptionHandlingMiddleware>();
}
}
Update:
I found this link https://github.com/aspnet/DependencyInjection/issues/578 which led me to change my Program.cs file's BuildWebHost method from this
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
to this
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
.Build();
}
I don't know what exactly is going on but it seems to work now.
You registered the IEmailRepository as a scoped service, in the Startup class.
This means that you can not inject it as a constructor parameter in Middleware because only Singleton services can be resolved by constructor injection in Middleware. You should move the dependency to the Invoke method like this:
public ExceptionHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, emailRepository);
}
}
Another way to get the instance of scoped dependency is to inject service provider (IServiceProvider) into the middleware constructor, create scope in Invoke method and then get the required service from the scope:
using (var scope = _serviceProvider.CreateScope()) {
var _emailRepository = scope.ServiceProvider.GetRequiredService<IEmailRepository>();
//do your stuff....
}
Check out Resolving Services in a Method Body in asp.net core dependency injection best practices tips tricks for more details.
Middleware is always a singleton so you can't have scoped dependencies as constructor dependencies in the constructor of your middleware.
Middleware supports method injection on the Invoke method,so you can just add the IEmailRepository emailRepository as a parameter to that method and it will be injected there and will be fine as scoped.
public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{
....
}
Your middleware and the service has to be compatible with each other in order to inject the service via the constructor of your middleware. Here, your middleware has been created as a convention-based middleware which means it acts as a singleton service and you have created your service as scoped-service. So, you cannot inject a scoped-service into the constructor of a singleton-service because it forces the scoped-service to act as a singleton one. However, here are your options.
Inject your service as a parameter to the InvokeAsync method.
Make your service a singleton one, if possible.
Transform your middleware to a factory-based one.
A Factory-based middleware is able to act as a scoped-service. So, you can inject another scoped-service via the constructor of that middleware. Below, I have shown you how to create a factory-based middleware.
This is only for demonstration. So, I have removed all the other code.
public class Startup
{
public Startup()
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TestMiddleware>();
services.AddScoped<TestService>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<TestMiddleware>();
}
}
The TestMiddleware:
public class TestMiddleware : IMiddleware
{
public TestMiddleware(TestService testService)
{
}
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
return next.Invoke(context);
}
}
The TestService:
public class TestService
{
}
In .NET Core 6, the below settings worked for me.
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider.GetRequiredService<IDbInitilizer>;
services.Invoke().Initialize();
}

Middleware DI Error

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)
{
//...
}

Categories

Resources