Only in the master branch in our project, a specific service injected in Startup does not resolve when calling a specific controller.
The injected "Service" is just a class with statics filled with configuration values.
In Startup.cs:
var azureAdConfiguration = Configuration.GetSection("AzureAD").Get<AzureAdConfiguration>();
services.AddSingleton<IAzureAdConfiguration>(azureAdConfiguration);
In the specific controller:
private readonly IAzureAdConfiguration _azureAdConfiguration;
public Controller(IConfiguration configuration, IAzureAdConfiguration azureAdConfiguration)
{
_configuration = configuration;
_azureAdConfiguration = azureAdConfiguration;
}
I have debugged startup, and the azureAdConfiguration object is properly filled from configuration.
What am i missing here? Why won't this resolve?
Related
I am migrating a web app from asp.net mvc to .net core (.net 5), and this has got me stuck.
The site is configured in IIS to accept request from multiple URLs like site1.example.com and site2.example.com. Each site has its own database, accessed through entity framework core.
In the old .net framework, I was able to use one of the events in the global.asax.cs to parse the incoming request URL and lookup the correct tenant database from a configuration file. I'm trying to set up something similar in asp.net core mvc.
Here's the relevant part of my ConfigureServices method in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>((serviceProvider, dbContextBuilder) =>
{
var tenantIdentifier = serviceProvider.GetRequiredService<ITenantIdentifier>();
var connectionString = Configuration.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
dbContextBuilder.UseSqlServer(connectionString);
}, ServiceLifetime.Scoped);
//other services configured below...
}
Then the tenant identifier looks like this:
public interface ITenantIdentifier
{
string GetCurrentTenantId();
}
public class UrlTenantIdentifier : ITenantIdentifier
{
readonly IHttpContextAccessor _httpContextAccessor;
readonly ILogger<UrlTenantIdentifier> _logger;
public UrlTenantIdentifier(IHttpContextAccessor httpContextAccessor, ILogger<UrlTenantIdentifier> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string GetCurrentTenantId()
{
//_httpContextAccessor is null here
//logic below for parsing URL and finding if we're site1 or site2
}
}
Is there a correct way of doing this now that I'm not aware of? How can I set up the entity framework database context for dependency injection when I don't know the connection string key until runtime? Am I going to be stuck configuring separate sites and virtual directories in IIS?
Refactor the DbContext to override the OnConfiguring member. Inject configuration and context accessor and perform configuration there.
public class myDbContext : DbContext {
private readonly ITenantIdentifier tenantIdentifier;
private readonly IConfiguration configuration;
public myDbContext(IConfiguration configuration, ITenantIdentifier tenantIdentifier) {
this.configuration = configuration;
this.tenantIdentifier = tenantIdentifier;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
var connectionString = configuration
.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
optionsBuilder.UseSqlServer(connectionString);
}
}
Trying to access the request context at the time the DbContext is being created/initialized is too early in the request flow to get access to the desired information. It needs to happen after the context has already been initialized and injected.
public void ConfigureServices(IServiceCollection services)
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>(); //Simplified since configuration is internal
//other services configured below...
}
Reference DbContext Lifetime, Configuration, and Initialization
While running the .Net Core 2.0 API endpoint getting below error.
A suitable constructor for type 'RestDataService' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
public partial class RestDataService : IRestDataService
{
private static HttpClient _client;
private static AppConfiguration _configuration;
private const short MaxRetryAttempts = 3;
private const short TimeSpanToWait = 2;
public RestDataService(AppConfiguration configuration)
{
_client = configuration.HttpClient;
_configuration = configuration;
}
........
And my startup class is something like this :
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var config = new AppConfiguration
{
Environment = Configuration["environment"],
};
services.AddMvc().AddJsonOptions(o => o.SerializerSettings.NullValueHandling = NullValueHandling.Include);
services.AddMemoryCache();
services.AddCors();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddSingleton(Configuration);
services.AddSingleton(config);
services.AddLogging();
services.AddTransient<IRestDataService, RestDataService>();
services.AddHttpClient<IRestDataService, RestDataService>()
.AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOp);
Any suggestions, to get rid of this? constructor is already public and all the parameters are registered in startup file
I received the error "A suitable constructor for type '<type>' could not be located." after accidentally generating a protected constructor instead of public constructor. Marking it back to public fixed it.
For AddHttpClient, you need to provide HttpClient parameter for RestDataService. And, you need to register AppConfiguration.
RestDataService
public class RestDataService: IRestDataService
{
private static HttpClient _client;
private static AppConfiguration _configuration;
private const short MaxRetryAttempts = 3;
private const short TimeSpanToWait = 2;
public RestDataService(AppConfiguration configuration
, HttpClient client)
{
_client = configuration.HttpClient;
_configuration = configuration;
}
}
Startup.cs
var config = new AppConfiguration
{
Environment = Configuration["environment"],
};
services.AddSingleton(typeof(AppConfiguration), config);
services.AddHttpClient<IRestDataService, RestDataService>();
I just ran across this issue because I was accidentally registering HttpClient to an interface.
public void ConfigureServices(IServiceCollection services) {
// !! Wrong, this is done automatically by AddHttpClient<IMyService, MyService>()
services.AddTransient<IMyService, MyService>();
// !! There is no suitable constructor for IMyService, since it is an interface.
services.AddHttpClient<IMyService>();
// Instead, let AddHttpClient manage your service's lifetime,
// and tell it the implementation.
// It is registered with a Transient lifetime as described below.
services.AddHttpClient<IMyService, MyService>();
}
Important context, from source: https://www.stevejgordon.co.uk/ihttpclientfactory-patterns-using-typed-clients-from-singleton-services
When defining typed clients in your ConfigureServices method, the typed service is registered with transient scope. This means that a new instance is created by the DI container every time one is needed. The reason this occurs is that a HttpClient instance is injected into the typed client instance. That HttpClient instance is intended to be short lived so that the HttpClientFactory can ensure that the underlying handlers (and connections) are released and recycled.
You have to define which concrete class of interface you want to use for IRestDataService. So, define like this.
services.AddTransient<IRestDataService, RestDataService>();
Remove static keyword before AppConfiguration.
private readonly AppConfiguration _configuration;
In my case I got this problem when inattentively duplicating row with DI service registration to register new service but accidentally missed that this is .AddHttpClient() method instead of .AddTransient() or .AddScoped()
This doesn't directly answer the question, but is related.
For anyone using ActivatorUtilities.CreateInstance<T>(IServiceProvider, params object[] parameters) it's worth mentioning that every parameter must be in the parameters of the class you are creating. I.e.
public class Example
{
public Example(AppConfiguration configuration)
{
...
}
}
_ = ActivatorUtilities.CreateInstance<Example>(_serviceProvider, new HttpClient())
This will fail as the Example class doesn't have a constructor that has a HttpClient paramter
I had forgotten to add a RequestDelegate next parameter to my middleware constructor. Without this, I would get an exception with the message "A suitable constructor for type [Type] could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor" upon calling IApplicationBuilder.UseMiddleware. My constructor was already public, and my services were all registered. I just needed to add RequestDelegate next to my parameters.
I try to setup the DI for a new ASP.NET Core site and I have this code:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Get the configuration from the app settings.
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Get app settings to configure things accordingly.
var appSettings = Configuration.GetSection("AppSettings");
var settings = new AppSettings();
appSettings.Bind(settings);
services
.AddOptions()
.Configure<AppSettings>(appSettings)
.AddSingleton<IConfigurationRoot>(config)
.AddDbContext<MyDbContext>(builder =>
{
builder.UseSqlServer(config.GetConnectionString("myConn"));
}, ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddSingleton<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
...
Now, the LoadTestCleanUpServiceRepository depends on the MyDbContext:
public class LoadTestCleanUpServiceRepository : ILoadTestCleanUpServiceRepository
{
private readonly MyDbContext _dbContext;
public LoadTestCleanUpServiceRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
...
..and the DB Context is this:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> ctxOptions) : base(ctxOptions)
{
}
}
When I run the application, I get this error:
InvalidOperationException: Unable to resolve service for type
'MyCode.Infrastructure.Common.MyDbContext' while attempting to
activate
'MyCode.Infrastructure.LoadTestCleanUpService.LoadTestCleanUpServiceRepository'.
I have tried changing the ServiceLifetime options and adding this extra code:
services.AddTransient<MyDbContext>(sp => new MyDbContext(config));
...but nothing seems to help and I cannot understand why this doesn't work. It does try to construct the repository, but why can't it construct the DB Context too? It doesn't even reach the point where I call UseSqlServer()!
Any ideas?
UPDATE 1:
Hmm... I now see this. Most likely it is related:
UPDATE 2:
I have now :
Replaced EF 6 with Microsoft.EntityFrameworkCore.SqlServer
Upgraded to netcoreapp2.2 target framework to solve some conflicting assembly versions.
Made the repository scoped.
But I still get the same error.
I see you have registered LoadTestCleanUpServiceRepository as Singleton while MyDbContext as Transient and then you are trying to resolve MyDbContext from LoadTestCleanUpServiceRepository. That's the problem. According to ASP.NET Core Service lifetimes documentation:
It's dangerous to resolve a scoped service/transient service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Solution is: register LoadTestCleanUpServiceRepository and MyDbContext as follows:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnectionStringName")));
services.AddScoped<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
Now problem should go away.
When I have nested classes where children need some of the configuration settings (i.e., settings written in appsettings.json), do I need to make a bucket relay to pass configuration to children classes?
I don't think the example below is a smart way. Is there any better practice?
Startup.cs
public Startup(IConfiguration configuration, ...)
{
...
this.Configuration = configuration;
...
}
Parent.cs
public class Parent
{
public Parent(IConfiguration configuration)
{
_configuration = configuration;
}
private IConfiguration _configuration;
private ChildOne _childOne;
private ChildTwo _childTwo;
public void InitializeChildren()
{
_childOne = new ChildOne(_configuration);
_childTwo = new ChildTwo(_configuration);
}
}
ChildOne.cs
public class ChildOne{
public ChildOne(IConfiguration configuration){
_propOne = configuration.GetSection("brahbrah").Value;
}
private string _propOne;
}
Domain objects / models are nothing more than data containers. These data containers can have a need for data but should not be dependent on dependency injection (directly) for this data because they are at the core of your application. A change in your model (or it's dependencies) will most likely result in bigger changes.
As you show in your examples you want to instantiate your models using the new operator and pass the IConfiguration as a parameter. By requiring IConfiguration in your data container you create a situation where your model will need extensive checking if the returned result exists and if every property in it exists and afterward setting the appropriate values in the data container.
A better solution to this problem is by registering a dedicated config class, which we will call BrahBrahConfig to match your example, in the dependency injection framework.
public static IServiceCollection SetupDependencyInjection(this
IServiceCollection services, IConfiguration config)
{
services.Configure<BrahBrahConfig>(config.GetSection("brahbrah"));
return services;
}
In the example above you see the use of an overload for IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) which can be found in the nuget package "Microsoft.Extensions.Options.ConfigurationExtensions".
This overload enables you to directly inject an instance of IOptions into the constructor of your choice.
private BrahBrahConfig _config;
public Parent(IOptions<BrahBrahConfig> config)
{
_config = config?.Value ?? throw new ArgumentNullException(nameof(config));
}
So after registering this in your startup.cs you can use IOptions as parameter in the Parent constructor and use these settings to set the appropriate properties in your model.
Use dependency injection for your services, not for your Models. Models should not have any logic or service registration.
If you are talking about Service Classes, they are generally part of DI. you can register them to DI so that DI automatically resolves services upon instance construction.
For Instance,
public class Parent
{
public Parent(IConfiguration configuration, ChildOne childOne, ChildTwo childTwo)
{
_configuration = configuration;
_childOne = childOne;
_childTwo = childTwo;
}
private IConfiguration _configuration;
private ChildOne _childOne;
private ChildTwo _childTwo;
}
If you need to initialize ChildOne and ChildTwo by yourself, then you need to pass IConfiguration parameter or at least IServiceProvider in order to resolve required service(s)
All of our business services were previously set up to use Dependency Injection with IOptions because they were being consumed by ASP.NET Core apps, like so:
NotificationDataAccess.cs:
public class NotificationDataAccess : BaseDataAccess, INotificationDac<Notification>
{
public NotificationDataAccess(IOptions<DataAccessConfiguration> options, IClaimsAccessor claimsAccessor) :
base(options, claimsAccessor)
{
}
}
NotificationBusinessService.cs:
public class NotificationBusinessServices : INotificationServices<Notification>
{
private readonly INotificationDac<Notification> _notificationDataAccess;
public NotificationBusinessServices(
INotificationDac<Notification> notifcationDataAccess)
{
_notificationDataAccess = notifcationDataAccess;
}
}
Now I'm left with the unenviable task of trying to figure out how to leverage the same pattern from a windows service, which doesn't benefit from the built-in ASP.NET Core features for handling DI. When the service starts up, I execute the following code:
// Set up configuration, services, and logging.
IServiceCollection services = new ServiceCollection();
var startup = new Startup();
startup.ConfigureServices(services);
IServiceProvider serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetService<IConfigurationRoot>();
var notificationService = serviceProvider.GetService<INotificationServices<Notification>>();// TODO: This errors!
processor = new Processor(configuration, notificationService);
And here is the Startup.cs code, which is supposed to configure the services:
public class Startup
{
IConfigurationRoot Configuration { get; }
public Startup()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigurationRoot>(Configuration);
//services.AddMvc();
// Add application services.
Listings.Business.Configuration.Instance = new BusinessLayerConfiguration();
services.Configure<DataAccessConfiguration>(options => Configuration.GetSection("Data").Bind(options));
services.AddScoped(typeof(INotificationDac<Notification>), typeof(NotificationDataAccess));
services.AddScoped(typeof(INotificationServices<Notification>), typeof(NotificationBusinessServices));
}
}
Unfortunately, when I run the windows service it throws an exception when trying to get the notificationService:
var notificationService = serviceProvider.GetService<INotificationServices<Notification>>();
The exception is:
System.InvalidOperationException: 'Unable to resolve service for type
'Microsoft.Extensions.Options.IOptions`1[Rpr.Listings.DataAccess.DataAccessConfiguration]'
while attempting to activate
'Rpr.Listings.DataAccess.NotificationDataAccess'.'
I was hoping my "services.Configure" code would resolve this, but alas no. Clearly I need to register IOptions in my Startup.cs, however I have no idea how to do so. Is this something that usually happens out of the box with ASP.NET MVC? Does "services.AddMvc();" normally register this binding correctly? I can call that, but would need to import a ton of ASP.NET MVC packages into my windows service, which I'm reluctant to do.
Please let me know how to register the IOptions binding correctly, thanks!
It turns out that all I was missing was:
services.AddOptions();
Once I added that, the IOptions binding was registered correctly!
In case it helps anyone, I had this issue in a console app and it was caused by creating the service provider
IServiceProvider serviceProvider = services.BuildServiceProvider();
before I'd registered my config
services.Configure<DataAccessConfiguration>(Configuration.GetSection("Data"));