ASP.Net Core MVC Dependency Injection not working - asp.net-mvc

I am trying to inject a interface into to my HomeController and I am getting this error:
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Configuration.IConfiguration' while attempting to activate
My Startup class is as follows:
public Startup(IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options => options
.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddSingleton(provider => Configuration);
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
try
{
using (var serviceScope = app.ApplicationServices
.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider
.GetService<ApplicationDbContext>()
.Database.Migrate();
}
}
catch { }
}
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseApplicationInsightsExceptionTelemetry();
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.Run((async (context) =>
{
await context.Response.WriteAsync("Error");
}));
}
and my HomeController constructor is:
public HomeController(IConfiguration configuration, IEmailSender mailService)
{
_mailService = mailService;
_to = configuration["emailAddress.Support"];
}
Please tell me where I am mistaken.
Microsoft.Extensions.DependencyInjection.ServiceLookup.Service.PopulateCallSites(ServiceProvider provider, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)

Try injecting it as an IConfigurationRoot instead of IConfiguration:
public HomeController(IConfigurationRoot configuration
, IEmailSender mailService)
{
_mailService = mailService;
_to = configuration["emailAddress.Support"];
}
In this case, the line
services.AddSingleton(provider => Configuration);
is equivalent to
services.AddSingleton<IConfigurationRoot>(provider => Configuration);
because the Configuration property on the class is declared as such, and injection will be done by matching whatever type it was registered as. We can replicate this pretty easily, which might make it clearer:
public interface IParent { }
public interface IChild : IParent { }
public class ConfigurationTester : IChild { }
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
IChild example = new ConfigurationTester();
services.AddSingleton(provider => example);
}
public class HomeController : Controller
{
public HomeController(IParent configuration)
{
// this will blow up
}
}
However
As stephen.vakil mentioned in the comments, it would be better to load your configuration file into a class, and then inject that class into controllers as needed. That would look something like this:
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
You can grab these configurations with the IOptions interface:
public HomeController(IOptions<AppSettings> appSettings)

In Core 2.0 it's recommended to use IConfiguration instead of IConfigurationRoot
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
from https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#add-configuration-providers

When moving a project from .Net Core 1.x to 2.0, change all IConfigurationRoot to IConfiguration

Related

How to inject and use Serilog (Ilogger) when web-api startup

I'm developing a .NET core 3.1 Console application (web-api).
I use a Serilog service (it is basically using the Microsoft.Extensions.Logging).
The Serilog is injected and can be used in the FW Controllers methods.
Now - I need something a little bit different - Whenever the system is starting up (after being down) I need to make an http post request - you can see it when executing the ConnectionInitiator.Initiate(), in the startup method. In that same scope (method\class) - I need to use the logger, in order to log some data. Now - If the request would be through the controller - the logger, would be available (by the DI).
To make a long story short - I need somehow to inject the Ilogger to the class or to make it available in some other way. I've tried use the logger in the startUp, but this seems to be impossible (since .net core 3.0 - if I understand correctly)
See my code:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var loggerConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
//Reading the appconfig.json
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(loggerConfig).CreateLogger();
try
{
Log.Information("System Started up");
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "THE APPLICATION FAILED TO START UP");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args).ConfigureLogging((context, logging) =>
{
logging.ClearProviders();
}).UseSerilog().UseStartup<Startup>();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
StartUp.cs
public class Startup
{
public Startup(IConfiguration configuration/*, Microsoft.Extensions.Logging.ILogger logger*/)
{
Configuration = configuration;
ConnectionInitiator.Initiate(configuration/*, logger*/);
}
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.AddCors();
services.AddControllers();
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().SetIsOriginAllowed((host) => true).AllowCredentials());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
ConnectionInitiator.cs:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace AAA.BL
{
public static class ConnectionInitiator
{
private static readonly object threadlock = new object();
private static MyConfig myConfig;
private static ILogger ilogger;
/*
public ConnectionInitiator(ILogger _logger)
{
ilogger = _logger;
}
*/
public static void/*async Task*/ Initiate(IConfiguration configuration/*, ILogger ilogger*/)
{
HttpRequester httpRequester = new HttpRequester();
if (myConfig == null)
{
myConfig = new myConfig(configuration);
}
IssueOTPResponse response = /*await*/ httpRequester.PostSomething(myConfig, ilogger).Result; //Double check thread safe singleton implementation
if (response.ststuacode != 200)
{
ilogger.logcritical($"critical error when initiate connection (connectioninitiator): {response.statusdescription}");
}
}
}
}
It seems like the answer is much simpler that I expected - By using the Serilog and was added as a service in the Configure method - It can be reached globally (in every place of the namepsace) by using the static class Log and its static method Logger, for example:
Log.Logger.Information("XXXXX");

swagger.json not generated for controller that inherits from a base class

In my net core api application all the controllers inherit from a base controller.
Because of this, swagger doesn't work.
I create a sample project with visual studio and swagger works fine
But when I create a base class (Test.cs) that inherits from ControllerBase and I change ValuesController.cs to inherit from Test instead of ControllerBase, swagger don't work.
I got the error
Failed to load API definition.
Fetch errorundefined /swagger/v1/swagger.json
Thank you for your help!
Here is my code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Test", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.XML";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("swagger.json", "Test");
});
}
Test.cs
using Microsoft.AspNetCore.Mvc;
namespace WebApplication6.Controllers
{
public class Test : ControllerBase
{
public void MyTest()
{
}
}
}
ValuesController.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication6.Controllers
{
[Route("api/[controller]")]
[ApiController]
// This works
// public class ValuesController : ControllerBase
// This doesn't works
public class ValuesController : Test
{
/// <summary>
/// Test
/// </summary>
/// <returns></returns>
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
}
I solved my problem changing my base class Test.cs
public void MyTest()
to
protected void MyTest()
to avoid this issue and exclude controller public method from Swagger API you could use following attribute:
[ApiExplorerSettings(IgnoreApi = true)]
public void MyTest()
{
}

Service Fabric with multiple endpoints and dependency injection

I'm currently working on a POC project and I'm trying to figure out how I can share a service dependency between different endpoints to control application state and handle all service requests (lets call it ControlService) - specifically when one of those endpoints is a KestrelCommunicationListener / HttpSysCommunicationListener and combined with a FabricTransportServiceRemotingListener (or any other type of custom listener)
Autofac looked promising but the examples don't show how to get a HTTP listener working when the container is built in startup rather than the main entry point - would I need to pass the container to MyFabricService so it can be passed into and added to by the startup registrations?
I've seen references to using container.Update() or adding registrations on the fly using container.BeginLifetimeScope() but they are all using a container built in main and then I'm not sure how I would add the APIs created by the HTTP listener to the original container.
I'm possibly not explaining it that well so in summary I'm looking to have something like the below service that can receive communications via n. different endpoints - process the message and then send messages out via n. clients (aka other service endpoints)
Happy to clarify if anything is unclear - perhaps even using another creative diagram :)
Updated:
From Program.Main()
ServiceRuntime.RegisterServiceAsync("ManagementServiceType",
context => new ManagementService(context)).GetAwaiter().GetResult();
Here is my fabric service
public ManagementService(StatefulServiceContext context)
: base(context)
{
//this does not work but is pretty much what I'm after
_managementService = ServiceProviderFactory.ServiceProvider.GetService(typeof(IManagementService)) as IManagementService;
}
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners() =>
new ServiceReplicaListener[]
{
//create external http listener
ServiceReplicaListenerFactory.CreateExternalListener(typeof(Startup), StateManager, (serviceContext, message) => ServiceEventSource.Current.ServiceMessage(serviceContext, message), "ServiceEndpoint"),
//create remoting listener with injected dependency
ServiceReplicaListenerFactory.CreateServiceReplicaListenerFor(() => new RemotingListenerService(_managementService), "ManagmentServiceRemotingEndpoint", "ManagementServiceListener")
};
ServiceReplicaListener
public static ServiceReplicaListener CreateExternalListener(Type startupType, IReliableStateManager stateManager, Action<StatefulServiceContext, string> loggingCallback, string endpointname)
{
return new ServiceReplicaListener(serviceContext =>
{
return new KestrelCommunicationListener(serviceContext, endpointname, (url, listener) =>
{
loggingCallback(serviceContext, $"Starting Kestrel on {url}");
return new WebHostBuilder().UseKestrel()
.ConfigureServices((hostingContext, services) =>
{
services.AddSingleton(serviceContext);
services.AddSingleton(stateManager);
services.AddApplicationInsightsTelemetry(hostingContext.Configuration);
services.AddSingleton<ITelemetryInitializer>((serviceProvider) => new FabricTelemetryInitializer(serviceContext));
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddServiceFabricConfiguration(serviceContext);
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddDebug();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseStartup(startupType)
.UseUrls(url)
.Build();
});
});
}
Startup
public class Startup
{
private const string apiTitle = "Management Service API";
private const string apiVersion = "v1";
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
var modules = new List<ICompositionModule>
{
new Composition.CompositionModule(),
new BusinessCompositionModule()
};
foreach (var module in modules)
{
module.AddServices(services, configuration);
}
services.AddSwashbuckle(configuration, apiTitle, apiVersion, "ManagementService.xml");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddApplicationInsights(app.ApplicationServices);
// app.UseAuthentication();
// app.UseSecurityContext();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseMvc();
app.UseSwagger(apiTitle, apiVersion);
//app.UseMvc(routes =>
//{
// routes.MapRoute(
// name: "default",
// template: "{controller=Home}/{action=Index}/{id?}");
//});
}
}
All the service dependencies are added in the CompositionModules using Microsoft.Extensions.DependencyInjection (not autofac) in startup.cs
This works great and creates my HTTP listener - I now just need a way of getting access to my services that were added to the container during startup of my http listener / webhost.
You can use Autofac.Integration.ServiceFabriŅ, an Autofac extension to support Service Fabric. You need to create a container in Program.cs
var builder = new ContainerBuilder();
builder.RegisterServiceFabricSupport();
builder.RegisterType<SomeService>().As<IManagementService>();
builder.RegisterStatelessService<ManagementService>("ManagementServiceType");
using (builder.Build())
{
// Prevents this host process from terminating so services keep running.
Thread.Sleep(Timeout.Infinite);
}
Then you can inject it to the constructor of your fabric service. You can find more information on this topic on https://alexmg.com/posts/introducing-the-autofac-integration-for-service-fabric
#Tim
Sorry for a late response. Currently I am working on library package that we use in our company for internal projects. This library simplifies configuration of Reliable Services. I think our recent enhancements can do what you need to do (hope I get the use case correctly).
All the information about the library can be found on project page on GitHub and NuGet package can be found here (please note that it is a pre-release version but we are planning to turn into complete release soon).
In case you have any questions or need more information feel free to contact me.
UPDATE
I have create a sample application. Please feel free to try it.
Here is a code example.
public interface IManagementService
{
string GetImportantValue();
}
public interface IMessageProvider
{
string GetMessage();
}
public class MessageProvider : IMessageProvider
{
public string GetMessage()
{
return "Value";
}
}
public class ManagementService : IManagementService
{
private readonly IMessageProvider provider;
public ManagementService(
IMessageProvider provider)
{
this.provider = provider;
}
public string GetImportantValue()
{
// Same instances should have the same hash
return this.provider.GetMessage() + $"Hash: {this.GetHashCode()}";
}
}
public interface IRemotingImplementation : IService
{
Task<string> RemotingGetImportantValue();
}
public class RemotingImplementation : IRemotingImplementation
{
private readonly IManagementService managementService;
public RemotingImplementation(
IManagementService managementService)
{
this.managementService = managementService;
}
public Task<string> RemotingGetImportantValue()
{
return Task.FromResult(this.managementService.GetImportantValue());
}
}
public class WebApiImplementationController : ControllerBase
{
private readonly IManagementService managementService;
public WebApiImplementationController(
IManagementService managementService)
{
this.managementService = managementService;
}
[HttpGet]
public Task<string> WebApiGetImportantValue()
{
return Task.FromResult(this.managementService.GetImportantValue());
}
}
public class WebApiStartup
{
private readonly IConfiguration configuration;
public WebApiStartup(
IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(
IServiceCollection services)
{
services.AddMvc();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseMvcWithDefaultRoute();
}
}
internal static class Program
{
/// <summary>
/// This is the entry point of the service host process.
/// </summary>
private static void Main()
{
var host = new HostBuilder()
.ConfigureServices(
services =>
{
services.AddTransient<IMessageProvider, MessageProvider>();
services.AddSingleton<IManagementService, ManagementService>();
})
.ConfigureStatefulService(
serviceBuilder =>
{
serviceBuilder
.UseServiceType("StatefulServiceType")
.DefineAspNetCoreListener(
listenerBuilder =>
{
listenerBuilder
.UseEndpointName("ServiceEndpoint")
.UseKestrel()
.UseUniqueServiceUrlIntegration()
.ConfigureWebHost(
webHostBuilder =>
{
webHostBuilder.UseStartup<WebApiStartup>();
});
})
.DefineRemotingListener(
listenerBuilder =>
{
listenerBuilder
.UseEndpointName("ServiceEndpoint2")
.UseImplementation<RemotingImplementation>();
});
})
.Build()
.Run();
}
}

Asp core, Object reference not set to an instance of an object in Repository pattern

I am working on an asp core 1.1 and i want to create a select repository pattern in my project.
my code in repository class :
public class AuthorReposetory : IDisposable
{
private static ApplicationDbContext _context;
public AuthorReposetory(ApplicationDbContext context)
{
_context = context;
}
public static List<Author> GetAuthorList()
{
List<Author> model = new List<Author>();
model = _context.authors.Select(a => new Author
{
AuthorId = a.AuthorId,
AuthorName = a.AuthorName,
AuthorDescription = a.AuthorDescription
}).ToList();
return model;
}
public void Dispose()
{
throw new NotImplementedException();
}
~AuthorReposetory()
{
Dispose();
}
}
and in controller
[HttpGet]
public IActionResult Index()
{
var q = AuthorReposetory.GetAuthorList();
return View(q);
}
Update
Here is my StartUp Class
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//define Connection string
services.AddDbContext<ApplicationDbContext>(option => option.UseSqlServer(Configuration.GetConnectionString("DefualtConnection")));
//For Create and use identity in database
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add framework services.
services.AddMvc();
services.AddAutoMapper();
services.AddPaging();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
//New Area For Admin
routes.MapRoute(
name: "Admin",
template: "{area:exists}/{controller=Admin}/{action=Index}/{id?}");
//New Area For User
routes.MapRoute(
name: "UserProfile",
template: "{area:exists}/{controller=UserProfile}/{action=Index}/{id?}");
//tranditional Routing
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
My Connection string in appsettings.json
{
"ConnectionStrings": {
"DefualtConnection" : "Data Source=.;Initial Catalog=DataBaseName;User ID = sa; Password = 123"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
The problem is Object reference not set to an instance of an object when run model = _context.authors.Select(a => new Author in repository.
In the above i show controller code, Repository class code and start up code to figure out.
Where can the problem be?
Note : everything is good in controller. just problem is in repository class.
You are using static methods and the constructor is never called as you never create the object. You would have to change your constructor to:
public static AuthorReposetory()
{
_context = new ApplicationDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefualtConnection")));
}
but that's quite bad practice since you have to setup the connection in the constructor and this also created a hard dependency on your context class.
A better solution would be to create a non static class
public class AuthorRepository : IAuthorRepository
{
private ApplicationDbContext _context;
public AuthorRepository(ApplicationDbContext context)
{
_context = context;
}
// ...
}
public interface IAuthorRepository
{
// ...
}
Reverse the dependency with injection:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//define Connection string and setup dependency injection
services.AddDbContext<ApplicationDbContext>(option => option.UseSqlServer(Configuration.GetConnectionString("DefualtConnection")));
services.AddScoped<IAuthorRepository, AuthorRepository>();
// ...
}
Controller:
public HomeController
{
private IAuthorRepository _authorRepository;
public HomeController(IAuthorRepository authorRepository)
{
_authorRepository = authorRepository;
}
[HttpGet]
public IActionResult Index()
{
var q = _autorRepository.GetAuthorList();
return View(q);
}
}
Probably your _context object is null. How is your DI/IoC configured? I would investigate in that way.
Your db context should be added like this :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options => options.UseSqlite("Data Source=blog.db"));
}
Here is the documentation on how to configure your db context: https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
You have not registered your repository class in the services so it is not getting resolved via the container. In your ConfigureServices method try this:
services.AddScoped<AuthorReposetory>();

Calling service/repository methods in ASP.Net Core middleware

ASP.Net Core noob here...I am using an ASP.Net Core WebAPI core project using DNX451 with EF 6.
I have a requirement to implement API Key auth in our service. To do this I have created middleware that gets information from the request and proceeds with authentication. It is SUPPOSED to go to the database, get the key to match, and then return and do the validation.
Here is the middleware implemented to look at the context and get the APIKey
AuthenticationHandler
public class AuthorizationHandler
{
private readonly RequestDelegate _next;
private IAuthenticationService _authenticationService;
public AuthorizationHandler(RequestDelegate next, IAuthenticationService authService)
{
_authenticationService = authService;
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = _authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}
}
Here is the startup class with context and middleware registration
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(k => new DbContext(Configuration["Data:Context:ConnectionString"]));
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.RegisterAuthorizationHeader();
app.RegisterAuthorization();
app.UseMvc();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
Here is Auth service
public interface IAuthenticationService
{
string GetApiKey(string location);
}
public class AuthenticationService: IAuthenticationService
{
private IApiKeyRepository _apiKeyRepository;
public AuthenticationService(IApiKeyRepository repo)
{
_apiKeyRepository= repo;
}
public string GetApiKey(string location)
{
return _apiKeyRepository.GetApiKeyByLocation(location);
}
}
The repo
public interface IApiRepository
{
string GetApiKeyByLocation(string location);
}
public class ApiRepository: IApiRepository
{
private DbContext _context;
public ApiRepository(DbContext context)
{
_context = context;
}
public string GetApiKeyByLocation(string location)
{
var apiRow = _context.ApiKeyStore.FirstOrDefault(a => a.Location == location);
return apiRow == null ? string.Empty : apiRow.APIKey;
}
}
When attempting this I get the following error:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of DbContext
and related classes are not guaranteed to be thread safe.
Now, when I debug this every break point is hit twice. I believe I understand WHY this issue is occurring but have no idea how to fix it.
Can someone give me an idea, please? Any better solution ideas?
To use scoped dependencies in a middleware (which is necessarily a singleton by definition), the best approach is to flow it as a parameter of InvokeAsync instead of flowing it via the constructor:
public async Task Invoke(HttpContext context, IAuthenticationService authenticationService)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}

Resources