How to access singletons from ConfigureServices? There's a reason that I can't use appsettings for few configs.
For example, let's say that I want to set swagger title and version from database, not appsettings. My actual problem is I want to set consul address from my database. The problem should be the same, that I need to access my database in ConfigureServices. I have a custom extension like this:
public static IServiceCollection AddConsulConfig(this IServiceCollection services, string address)
{
services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>
{
consulConfig.Address = new Uri(address);
}));
return services;
}
I call it from startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
//I want to use IGlobalParameter here directly but without BuildServiceProvider
//This part is the problem
var service = ??
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(varConsulAddress);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = varTitle, Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// I can use it here or in the controller no problem
var service = app.ApplicationServices.GetRequiredService<IGlobalParameter>();
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
}
I DO NOT want to use BuildServiceProvider as it will make multiple instances, even visual studio gives warning about it. referenced in How to Resolve Instance Inside ConfigureServices in ASP.NET Core
I knew the existence of IConfigureOptions from the following link
https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer
But, I can't seem to find how exactly do I use that in ConfigureService:
public class ConsulOptions : IConfigureOptions<IServiceCollection>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConsulOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(IServiceCollection services)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
IGlobalParameter globalParameter = provider.GetRequiredService<IGlobalParameter>();
var ConsulAddress = globalParameter.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(ConsulAddress);
}
}
}
Set it in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
services.AddSingleton<IConfigureOptions<IServiceCollection>, ConsulOptions>(); // So what? it's not called
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// IConsulClient is still null here
}
Any solution to how do I achieve this?
Thank you Jeremy, it's as simple as that. I don't know why I spend way too much time figuring out how to set this
The solution is to add singleton :
services.AddSingleton<IConsulClient, ConsulClient>(
p => new ConsulClient(consulConfig =>
{
var ConsulAddress = p.GetRequiredService<IGlobalParameter>().GetById("ConsulAddress").Result.Value;
consulConfig.Address = new Uri(ConsulAddress);
}
));
Related
I am working on a .NET Core 3.1 application. I have a requirement where i have to inject a service in Startup.cs. My code is:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(servicesCollection =>
{
servicesCollection.AddScoped<IUnauthorizedAccessService, UnauthorizedAccessService>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs:
public Startup(IConfiguration configuration, IUnauthorizedAccessService unauthorizedAccessService)
{
Configuration = configuration;
_unauthorizedAccessService = unauthorizedAccessService;
}
public IConfiguration Configuration { get; }
public IUnauthorizedAccessService _unauthorizedAccessService { get; set; }
When i run the code, i get the following exception:
Unable to resolve service for type 'Interface.Service.IUnauthorizedAccessService' while attempting to activate 'Website.Startup'.'
How can i inject the service in Startup.cs ? I have even tried it getting in Configure method. But then, i get the exception at repository level. Code:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IUnauthorizedAccessService unauthorizedAccessService)
{
_unauthorizedAccessService = unauthorizedAccessService;
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.Strict,
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=User}/{action=Index}/{id?}");
});
}
I have a method RegisterDatabase which is being called from ConfigureServices. Code:
private void RegisterDatabase(IServiceCollection services)
{
services.AddDbContext<TrainingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
Service code is:
public class UnauthorizedAccessService : IUnauthorizedAccessService
{
private readonly IEventLogRepository _eventLogRepository;
public UnauthorizedAccessService(IEventLogRepository eventLogRepository)
{
_eventLogRepository = eventLogRepository;
}
public async Task<BaseResponse> LogUnauthorizedAccessInDB(string user, string url, string sessionId)
{
try
{
EventLog eventLog = new EventLog();
eventLog.Httpsession = sessionId;
eventLog.AppUserName = user;
eventLog.EventDateTime = DateTime.Now;
eventLog.MessageLevel = 3;
eventLog.Message = url;
await _eventLogRepository.Add(eventLog);
}
catch(Exception ex)
{
}
return HelperService.Response(null, null);
}
}
When Adding the object, i get the exception
Cannot access a disposed context instance. A common cause of this error is disposing a context instance 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 instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'TrainingContext'.
All of my other repositories are working but, getting exception only at this point. What can be the possible issue ? Any help would be much appreciated.
Basically, what i am trying to achieve is that i want to log unauthorized access to my site in Database. Code is:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.AccessDeniedPath = "/Home/Error";
o.LoginPath = "/Login";
o.SlidingExpiration = false;
o.Events = new CookieAuthenticationEvents
{
//OnRedirectToAccessDenied = new Func<RedirectContext<CookieAuthenticationOptions>, Task>(context =>
OnRedirectToAccessDenied = new Func<RedirectContext<CookieAuthenticationOptions>, Task>(test)
};
});
test method is:
private async Task<Task> test (RedirectContext<CookieAuthenticationOptions> context)
{
string user = context.HttpContext.User.Identity.Name;
string url = "/" + context.Request.Host.Value + "/" + context.Request.RouteValues["controller"] + "/" + context.Request.RouteValues["action"];
string sessionId = context.HttpContext.Session.Id;
await _unauthorizedAccessService.LogUnauthorizedAccessInDB(user, url, sessionId);
context.Response.Redirect("/Home/Error");
return context.Response.CompleteAsync();
}
You need to create a scoped object that implements CookieAuthenticationEvents. For example:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Threading.Tasks;
namespace MyApplication.Services
{
public class MyCookieAuthenticationEvents : CookieAuthenticationEvents
{
private readonly IUnauthorizedAccessService _unauthorizedAccessService;
public MyCookieAuthenticationEvents(
IUnauthorizedAccessService unauthorizedAccessService)
{
_unauthorizedAccessService = unauthorizedAccessService;
}
public override Task RedirectToAccessDenied(
RedirectContext<CookieAuthenticationOptions> context)
{
// TODO: you can use _unauthorizedAccessService here
return base.RedirectToAccessDenied(context);
}
}
}
To inject this, you'd do it as so:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(MyCookieAuthenticationEvents);
});
services.AddScoped<MyCookieAuthenticationEvents>();
services.AddScoped<IUnauthorizedAccessService, UnauthorizedAccessService>();
Make sure you remove that IUnauthorizedAccessService from your program.cs. You don't inject there. You inject in your Configure method.
This is how you do proper dependency injection. You don't do what the accepted answer is doing. That is probably one of the most unorthodox things I have ever seen in a long time.
Startup.cs is designed for configuring own services and pipeline configuration. You can not inject your custom services in constructor just because they are not configured yet.
Docs:
The host provides services that are available to the Startup class
constructor. The app adds additional services via ConfigureServices.
Both the host and app services are available in Configure and
throughout the app.
I have a default web api template project using .NET Core 3.1 and I have registered Elastic Search NEST on my startup.cs. But when I load it, it hit error at
Singleton ImplementationType: Unable to resolve service for type 'Nest.IElasticClient' while attempting to activate in program.cs
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
and here is my 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)
{
services.AddControllers();
services.AddSingleton<IProductService, ESProductService>();
services.Configure<ProductSettings>(Configuration.GetSection("product"));
services.AddElasticsearch(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
and below is the ElasticsearchExtensions extension class
public static class ElasticsearchExtensions
{
public static void AddElasticsearch(this IServiceCollection services, IConfiguration configuration)
{
var url = configuration["elasticsearch:url"];
var defaultIndex = configuration["elasticsearch:index"];
var settings = new ConnectionSettings(new Uri(url))
.DefaultIndex(defaultIndex);
AddDefaultMappings(settings);
var client = new ElasticClient(settings);
services.AddSingleton(client);
CreateIndex(client, defaultIndex);
}
private static void AddDefaultMappings(ConnectionSettings settings)
{
settings
.DefaultMappingFor<Product>(m => m
.Ignore(p => p.Price)
.Ignore(p => p.Quantity)
.Ignore(p => p.Rating)
);
}
private static void CreateIndex(IElasticClient client, string indexName)
{
var createIndexResponse = client.Indices.Create(indexName,
index => index.Map<Product>(x => x.AutoMap())
);
}
}
Problem solved. The AddSingleton miss out the interface. services.AddSingleton<IElasticClient>(client);
I also got the same error. To fix the issue, I used IElasticClientService instead of IElasticClient. You can use below code for dependency Injection.
builder.Services.AddScoped<IElasticClientService, ElasticClientService>();
ElasticClientService has a property called "elasticClient" which can be used to do all sort of operations. Hope this helps.
As far as I have been able to do is to add new services though the IServiceCollection
Is there a way to add and remove these services at run time?
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
AzureMultiTenantServiceBuilders.Build(services);
}
I am trying to figure out how to configure additional AzureAd for our tenants at run time without having to restart the system.
public static class AzureMultiTenantServiceBuilders
{
public static void Build(IServiceCollection services)
{
foreach (var tenant in Tenant.GetAll())
{
services.AddAuthentication()
.AddAzureADTenanted(options =>
{
options.ClientId = tenant.ClientId;
options.TenantId = tenant.TenantId;
options.Instance = "https://login.microsoftonline.com";
});
}
I found this Installing a new middleware at runtime in ASP.Net Core which was close as its adding middleware but what i am trying to do is adding a service at runtime so this isnt helping.
public static class RuntimeMiddlewareExtensions
{
public static IServiceCollection AddRuntimeMiddleware(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
services.Add(new ServiceDescriptor(typeof(RuntimeMiddlewareService), typeof(RuntimeMiddlewareService), lifetime));
AzureMultiTenantServiceBuilders.Build(services);
return services;
}
public static IApplicationBuilder UseRuntimeMiddleware(this IApplicationBuilder app, Action<IApplicationBuilder> defaultAction = null)
{
var service = app.ApplicationServices.GetRequiredService<RuntimeMiddlewareService>();
service.Use(app);
if (defaultAction != null)
{
service.Configure(defaultAction);
}
return app;
}
}
My web application needs to let an admin user add and remove served folders from a .net core 2 app. I have found a way to provide a list of served folders, but I can't find a way to dynamically add or remove them once the app has been configured.
How do I re-run the configure function from within the application? Alternatively, how do I add or remove UseFileServer() configurations within an already-running service?
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDeveloperExceptionPage();
app.UseMvc();
//get these dynamically from the database
var locations = new Dictionary<string, string>{
{#"C:\folder1", "/folder1"},
{#"D:\folder2", "/folder2"}
};
foreach (var kvp in locations)
{
app.UseFileServer(new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(
kvp.Key
),
RequestPath = new PathString(kvp.Value),
EnableDirectoryBrowsing = true
});
}
}
}
I'm using .net core 2.0.0-preview2-final.
You may want to dynamically inject the FileServer middleware based on your settings.
There is an example project on Microsoft's Chris Ross' Github: https://github.com/Tratcher/MiddlewareInjector/tree/master/MiddlewareInjector
You'll have to add the MiddlewareInjectorOptions, MiddlewareInjectorMiddleware and MiddlewareInjectorExtensions classes from the aforementioned repo to your project.
Then, in your Startup class, register the MiddlewareInjectorOptions as a singleton (so it's available throughout your application) and use the MiddlewareInjector:
public class Startup
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<MiddlewareInjectorOptions>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
var injectorOptions = app.ApplicationServices.GetService<MiddlewareInjectorOptions>();
app.UseMiddlewareInjector(injectorOptions);
app.UseWelcomePage();
}
}
Then, inject the MiddlewareInjectorOptions wherever you want and configure the middleware dynamically, like this:
public class FileServerConfigurator
{
private readonly MiddlewareInjectorOptions middlewareInjectorOptions;
public FileServerConfigurator(MiddlewareInjectorOptions middlewareInjectorOptions)
{
this.middlewareInjectorOptions = middlewareInjectorOptions;
}
public void SetPath(string requestPath, string physicalPath)
{
var fileProvider = new PhysicalFileProvider(physicalPath);
middlewareInjectorOptions.InjectMiddleware(app =>
{
app.UseFileServer(new FileServerOptions
{
RequestPath = requestPath,
FileProvider = fileProvider,
EnableDirectoryBrowsing = true
});
});
}
}
Note that this MiddlewareInjector can inject just a single middleware, so your code should call UseFileServer() for each path you want to serve.
I've created a Gist with the required code: https://gist.github.com/michaldudak/4eb6b0b26405543cff4c4f01a51ea869
I want to access my Options instance which is added as singleton in ConfigureServices. Here is my code:
public class Startup
{
private IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton((serviceProvider) => ConfigurationBinder.Bind<Options>(Configuration));
}
public void Configure(IApplicationBuilder app)
{
var root = ""; // I want to access my Options instance to get root from it
var fileServerOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(root)
};
app.UseFileServer(fileServerOptions);
}
}
My question is how to access instance of Options in Configure method to set root variable.
As suggested in How to use ConfigurationBinder in Configure method of startup.cs, the runtime can inject the options directly into the Configure method:
public void Configure(IApplicationBuilder app, Options options)
{
// do something with options
}
According to Joe Audette's comment this is the solution:
var options = app.ApplicationServices.GetService<Options>();