inject all services in startup.cs, .net core, overloaded - c#

This is more a general question.
But I have a question about injecting services in the startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
})
.AddFluentValidation(config =>
{
config.RegisterValidatorsFromAssemblyContaining<Create>();
});
services.AddApplicationServices(Configuration);
services.AddIdentityServices(Configuration);
}
So anyway I split the services in other file:
public static class StartupExtensionClass
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services,
IConfiguration Configuration)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
});
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddCors(opt =>
{
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyMethod().AllowAnyHeader().WithOrigins("http://localhost:3000");
});
});
services.AddMediatR(typeof(List.Handler).Assembly);
services.AddAutoMapper(typeof(MappingProfiles).Assembly);
services.AddScoped<IUserAccessor, UserAccessor >();
services.AddScoped<IPhotoAccessor, PhotoAccessor>();
services.Configure<CloudinarySettings>(Configuration.GetSection("Cloudinary"));
return services;
}
But for example if you have hundreds of services. IS that not overloading the application in the start phase?
Because all the services will initialized in the beginning.
Is there not a way just to initialise some services when they will directly been called.
Like lazy loading
Thank you

I believe you are just setting up what the runtime will use when a class of the specified type is asked for....you aren't creating the classes immediately. So if your application just started up, but you never made any request to it, it isn't like 100 classes were just instantiated for no reason.

Related

service AutoMapper can't be resolved

this might be a duplicate of Unable to resolve service for type 'AutoMapper.Mapper' but it's been a year since that was asked and I have a slightly different setup, I think.
I have a .NET 5.0 webapi project, that has a startup class that looks like this
public class Startup
{
private readonly IConfiguration _config;
public Startup(IConfiguration config)
{
_config = config;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationServices(_config); //This is to keep the Startup class clean
services.AddControllers();
services.AddCors();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ts.API v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod()
.WithOrigins("https://localhost:4200"));
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I have a separate ApplicationServiceExtension that handles services. This is that class. This is where I call AddAutoMapper. I've tried skipping this and putting it directly into the Startup.cs but that didn't make a difference.
public static class ApplicationServiceExtensions
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration config)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(config.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("ts.Data"));
});
return services;
}
}
In a separate project (a console project) I handle everything to do with data coming from the database. This is also where I use a UserRepository.cs that extends IUserRepository. I hold all my DTOs there as well as my AutoMapper Profiles. Basically, I don't even need AutoMapper in my webapi project but I don't know how else to get it running other than adding it to the Startup.cs. Maybe I should mention that I'm pretty new to .NET core/5.0 and haven't really used AutoMapper before let alone set it up from scratch.
The error I'm getting looks like this
Unhandled exception. System.AggregateException: Some services are not able to be constructed
(Error while validating the service descriptor 'ServiceType: ts.Data.Interfaces.IUserRepository Lifetime:
Scoped ImplementationType: ts.Data.Repositories.UserRepository': Unable to resolve service for type 'AutoMapper.Mapper'
while attempting to activate 'ts.Data.Repositories.UserRepository'.)
Just in case you would like to see it, here is my UserRepository.
public class UserRepository : IUserRepository
{
private readonly DataContext _context;
private readonly Mapper _mapper;
public UserRepository(DataContext context, Mapper mapper)
{
_mapper = mapper;
_context = context;
}
public async Task<IEnumerable<UserDto>> GetAllAsync()
{
return await _context.Users
.ProjectTo<UserDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
}
If anyone has any idea why I'm getting this error, I'd really appreciate the help. I've been stuck with this too long and it's probably something super simple too.
Remove Assembly and try.
services.AddAutoMapper(typeof(AutoMapperProfiles));
At the end of the Startup.cs add following method
private static void RegisterServices(IServiceCollection services, IConfiguration config)
{
ApplicationServiceExtensions.AddApplicationServices(services, config);
}
Call RegisterServices and pass services and _config at end of ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
});
RegisterServices(services, _config);
}
And make AddApplicationServices void, move AddAutoMapper to the top
public static class ApplicationServiceExtensions
{
public static void AddApplicationServices(IServiceCollection services, IConfiguration config)
{
services.AddAutoMapper(typeof(AutoMapperProfiles));
services.AddScoped<IUserRepository, UserRepository>();
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(config.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("ts.Data"));
});
}
}
Also AutoMapperProfiles should inherit Profile
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<Initiative, InitiativeViewModel>();
}
}
Try adding automapper like this:
services.AddAutoMapper(configuration => configuration
.AddProfile<AutoMapperProfiles>(), typeof(Startup));
And inject IMapper instead of Mapper in your UserRepository.

How do I use a value from appsettings.json in my startup.cs ? - Not instantiate but actually use the value

If the following is my ConfigureServices in startup.cs, how do I get the value "http://example.com" from my appsettings? I do not want the string "http://example.com" to be explicitly mentioned in my startup.cs - rather I want to use IOptions and then get the value there.
This is what my appsettings currently looks like
"CORSOriginHosts": {
"AllowedHosts": "http://136.243.73.96:1337"
},
I have already created an Options class called CORSOriginHosts
This is what my startup currently looks like
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CORSOriginHosts>(Configuration.GetSection("CORSOriginHosts"));
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com");
});
});
services.AddControllers();
}
This is what I want it to look like
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CORSOriginHosts>(Configuration.GetSection("CORSOriginHosts"));
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
//NO STRINGS PRESENT HERE - this is sample code
builder.WithOrigins(CORSOriginHosts.Value.AllowedHost);
});
});
services.AddControllers();
}
Thanks!
Why you can´t just use Configuration.GetSection("CORSOriginHosts") instead of building an IOptions just to pass the value in this point? It seens to me you´re not gonna use this
CORSOriginHosts class anywhere else, so why bother creating it?
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins(Configuration.GetSection("CORSOriginHosts"));
});
});
services.AddControllers();
}

Is there a way to reduce how many service injections I have on Startup class in ASP.NET core?

I am developing an API using .net core and I am about to use dependency injection but I realized that having the ConfigureServices(IServiceCollection services) method in the Startup class clogged with service injections is what I do not like. For example...
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));
services.AddScoped<IServiceOne, ServiceOne>();
services.AddScoped<IServiceTwo, ServiceTwo>();
services.AddScoped<IServiceThree, ServiceThree>();
services.AddScoped<IServiceFour, ServiceFour>();
//more and more services. This could go on and on and that would make the whole class ugly
}
Is there a way I can put all these services in one class and call them in the Startup class?
I'm using asp.net3.1.
Thanks.
AddDbContext is an extension method that configures and registers a DbContext-derived class with IServicesCollection. You can use the same pattern and create your own extension methods that add specific services, eg :
static class MyExtensions
{
static IServiceCollection AddMyServices(this IServiceCollection services)
{
services.AddScoped<IServiceOne, ServiceOne>();
services.AddScoped<IServiceTwo, ServiceTwo>();
services.AddScoped<IServiceThree, ServiceThree>();
services.AddScoped<IServiceFour, ServiceFour>();
return services;
}
}
You could split the registrations into related services eg AddAccounting, AddInventory based on your application's modules or scenarios, putting all the registrations needed for a specific module in one place, eg :
static IServiceCollection AddAccounting(this IServiceCollection services)
{
var connection=Configuration.GetConnectionString("MyConnection");
services.AddDbContext<Account>(options => options.UseSqlServer(connection));
services.AddDbContext<Transaction>(options => options.UseSqlServer(connection));
services.AddScoped<IServiceOne, ServiceOne>();
services.AddScoped<IServiceTwo, ServiceTwo>();
return services;
}
static IServiceCollection AddInventory(this IServiceCollection services)
{
var connection=Configuration.GetConnectionString("MyConnection");
services.AddDbContext<Warehouse>(options => options.UseSqlServer(connection));
services.AddDbContext<Product>(options => options.UseSqlServer(connection));
services.AddScoped<IServiceThree, ServiceThree>();
return services;
}

ASP.NET Core IOC: ASP0000 Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created [duplicate]

I just pasted the 4 lines at the end from another project and it works but I get a warning.. I clearly do not understand DI well enough ... What does it want me to change ?
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.EnvironmentName == "Local")
{
services.AddHealthChecksUI()
.AddHealthChecks()
.AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
}
services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));
services.AddMvc(o =>
{
o.Filters.Add<CustomExceptionFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", b => b
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSwaggerDocument();
services.AddHttpContextAccessor();
services.AddAutoMapper(typeof(ObjectMapperProfile));
services.AddTransient<IEmailSender, EmailSender>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
services.AddScoped<IRfReportRepository, RfReportRepository>();
services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
services.AddScoped<IRfReportService, RfReportService>();
services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
ServiceProvider serviceProvider = services.BuildServiceProvider(); //WARNING IS HERE
IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
}
If called BuildServiceProvider() in ConfigureServices, shown warning "Calling 'BuildServiceProvider' from application code results in a additional copy of Singleton services being created"
I solved this issue:
Create another function (which passed argument is IServiceCollection) and into the function call BuildServiceProvider()
For example your code it should be:
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.EnvironmentName == "Local")
{
services.AddHealthChecksUI()
.AddHealthChecks()
.AddCheck<TestWebApiControllerHealthCheck>("HomePageHealthCheck")
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
}
services.Configure<PwdrsSettings>(Configuration.GetSection("MySettings"));
services.AddDbContext<PwdrsContext>(o => o.UseSqlServer(Configuration.GetConnectionString("PwdrsConnectionRoot")));
services.AddMvc(o =>
{
o.Filters.Add<CustomExceptionFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", b => b
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSwaggerDocument();
services.AddHttpContextAccessor();
services.AddAutoMapper(typeof(ObjectMapperProfile));
services.AddTransient<IEmailSender, EmailSender>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(Repository<>));
services.AddScoped<IRfReportTypeRepository, RfReportTypeRepository>();
services.AddScoped<IRfReportRepository, RfReportRepository>();
services.AddScoped<IRfReportLookupsService, RfReportLookupsService>();
services.AddScoped<IRfReportService, RfReportService>();
RegisterSerilogLogger logger = CreateRegisterSerilogLogger(services);
}
private RegisterSerilogLogger CreateRegisterSerilogLogger(IServiceCollection services){
services.Configure<RAFLogging>(Configuration.GetSection("RAFLogging"));
ServiceProvider serviceProvider = services.BuildServiceProvider(); //No warning here ))
IOptions<RAFLogging> RAFLogger = serviceProvider.GetRequiredService<IOptions<RAFLogging>>();
RegisterSerilogLogger logger = new RegisterSerilogLogger(RAFLogger);
return logger;
}
Or use ApplicationServices of IApplicationBuilder. ApplicationSerivces's type is IServiceProvider.
I mention this solution is only for remove warning.
Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.
UPDATED 24.01.2021
I read Adam Freeman's Pro ASP.NET Core 3 8th book. Adam Freeman used app.ApplicationServices instead of services.BuildServiceProvider() in page 157 for this purpose, that app is Configure method's parameter that this method located in Startup.cs
I thinks correct version is to use ApplicationServices property of app, which app is IApplicationBuilder in Configure method's parameter. ApplicationServices's type is IServiceProvider.
Adam Freeman's Pro ASP.NET Core 3 8th book : Pro ASP.NET Core 3
Adam Freeman's example project: SportStore project's Startup.cs, SportStore project's SeedData.cs
Microsoft's recommendations about DI : Dependency injection in ASP.NET Core
Similar questions' answers in Stackoverflow: https://stackoverflow.com/a/56058498/8810311, https://stackoverflow.com/a/56278027/8810311
The ONLY purpose of calling 'BuildServiceProvider' is to get a service provider instance,
To remove this call and still be able to use IServiceProvider, change Configure method to get it as parameter:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider provider)

Dependency Injection on Authorization Policy

I want to create a claim based authorization for my ASP.NET Core app:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
The problem is that I have a non trivial method to resolve the employee numbers (1 to 5) and I want to use a DI service:
public interface IEmployeeProvider {
string[] GetAuthorizedEmployeeIds();
}
I would like to inject this service and use it in AddPolicy, something like:
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", *employeeProvider.GetAuthorizedEmployeeIds()));
});
Note
I know that I can write my own AuthorizationHandler where I can easily inject IEmployeeProvider but I'm against this pattern because:
There is a already a handler that does exactly what I need
I need to write a new handler for each claim type and each different requirement
This is an anti pattern because the employee ids should really be part of the requirement while the handler should be generic component that handles the requirements
So I'm looking for a way to inject services when the policy is being built
To supplement the provided answer by #MichaelShterenberg, the configuration delegate can use a IServiceProvider to allow for additional dependencies
public static IServiceCollection AddAuthorization(this IServiceCollection services,
Action<AuthorizationOptions, IServiceProvider> configure) {
services.AddOptions<AuthorizationOptions>().Configure<IServiceProvider>(configure);
return services.AddAuthorization();
}
Which, based on the original example, can be used
public void ConfigureServices(IServiceCollection services) {
//...
service.AddScoped<IEmployeeProvider, EmployeeProvider>();
services.AddAuthorization((options, sp) => {
IEmployeeProvider employeeProvider = sp.GetRequiredService<IEmployeeProvider>();
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
//...
}
If there were other dependencies needed, they could be resolved from the service provider.
Thanks to Nkosi for the tip!
Since AddAuthorization is basically configuring AuthorizationOptions behind the scenes, I followed the same pattern only I used OptionsBuilder to configure options with dependencies
I created my own AddAuthorization method that accepts dependencies:
public static IServiceCollection AddAuthorization<TDep>(
this IServiceCollection services,
Action<AuthorizationOptions, TDep> configure) where TDep : class
{
services.AddOptions<AuthorizationOptions>().Configure<TDep>(configure);
return services.AddAuthorization();
}
And now I can use it to properly configure the requirement:
services.AddAuthorization<IEmployeeProvider>((options, employeeProvider> =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
You can follow the same technique if you need more dependencies (OptionsBuilder.Configure supports up to 5 dependencies)
Obviously, this solution requires extra validation when upgrading to newer ASP versions, as the underlying implementation of AddAuhtorization may change
You can build a service provider using the BuildServiceProvider() method on the IServiceCollection:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IEmployeeProvider, EmployeeProvider>();
var sp = services.BuildServiceProvider();
var employeeProvider = sp.GetService<IEmployeeProvider>();
string[] values = employeeProvider.GetAuthorizedEmployeeIds();
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds()));
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
interface and Class
public interface IEmployeeProvider
{
string[] GetAuthorizedEmployeeIds();
}
public class EmployeeProvider : IEmployeeProvider
{
public string[] GetAuthorizedEmployeeIds()
{
var data = new string[] { "1", "2", "3", "4", "5" };
return data;
}
}

Categories