I'm trying to inject a service into a ValidationHandler that inherits from JwtSecurityTokenHandler which validates the Jwt's signature. Unfortunately, to use the handler, I have to use object initialization with new in ConfigureServices, which means I can't use the injected services that comes with adding the service to the dependency container.
public class DynamicKeyJwtValidationHandler : JwtSecurityTokenHandler
{
private readonly IMemoryCache _cache;
public DynamicKeyJwtValidationHandler(IMemoryCache cache)
{
_cache = cache;
}
}
services.AddTransient<DynamicKeyJwtValidationHandler>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.SecurityTokenValidators.Clear();
opts.SecurityTokenValidators.Add(new DynamicKeyJwtValidationHandler(???));
});
So what can I do to still be able to use the IMemoryCache?
You can create an implementation of IConfigureNamedOptions<JwtBearerOptions>:
public class JwtOptionsConfigurer : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly DynamicKeyJwtValidationHandler _tokenValidator;
public JwtOptionsConfigurer(DynamicKeyJwtValidationHandler tokenValidator)
{
_tokenValidator = tokenValidator;
}
public void Configure(string name, JwtBearerOptions options)
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(_tokenValidator);
}
public void Configure(JwtBearerOptions options)
{
Configure(JwtBearerDefaults.AuthenticationScheme, options);
}
}
And then add it like so:
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtOptionsConfigurer>();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
We still need to call .AddJwtBearer() because that does some necessary registrations, etc.
Side note (in case it's useful to anyone): the authentication middleware creates a new JwtBearerOptions every time it is needed, so the configuration code above will be run multiple times.
Related
I want to get a registered service from within the AddAuthentication() method but I cannot do so without re-registering all the services again (in BuildServiceProvider).
I get the warning:
"Calling buildserviceprovider from application code results in an additional copy of services."
Is there a way to pass in IServiceCollection? It seems odd it is not already available seeing as I have access to "builder.Services".
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var context = builder.Services.BuildServiceProvider().GetService<IHttpContextAccessor>();
//I want to do this but it's not available.:
options.GetService<IHttpContextAccessor>();
//OR
builder.Services.GetService<IHttpContextAccessor>();
}
First implement IConfigureNamedOptions
public class ConfigurationsJwtBearerOptions : IConfigureNamedOptions<ConfigurationsJwtBearerOptions>
{
IHttpContextAccessor _httpContext;
public ConfigurationsJwtBearerOptions(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
public void Configure(string name, ConfigurationsJwtBearerOptions options)
{
Configure(options);
}
public void Configure(ConfigurationsJwtBearerOptions options)
{
//same code that you usually used in AddJwtBearer (options=>{})
}
}
Then in Progam.cs or StarUp.cs
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.ConfigureOptions<ConfigurationsJwtBearerOptions>().AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();//no need to configurate JwtBearer options here ConfigurationsJwtBearerOptions will handle it
I want at runtime to be able to register HttpClient(s) via IHttpClientFactory with a given Polly policies at runtime. Is it possible to somehow alter the IServiceCollection ?
I know IServiceCollection is a container that is used only for initialization and afterwards services should be resolved with IServiceProvider , but i need to add new HttpClient(s) at runtime based on given parameter:
Register service
public class UrlPolicyService : IUrlPolicyService
{
private IServiceCollection serviceCollection;
public void AddUrl(string url)
{
AsyncPolicyWrap<HttpResponseMessage> policy = CreatePolicy();
serviceCollection
.AddHttpClient(url)
.AddTransientHttpErrorPolicy(handler => policy);
}
public AsyncPolicyWrap<HttpResponseMessage> CreatePolicy()
{
return null;/some policy logic
}
public UrlPolicyService(IServiceCollection services)
{
this.serviceCollection = services;
}
}
Consumer
public void SomeHttpService
{
private IHttpClientFactory factory;
public async Task DoRequestWithPolicy(string url)
{
var policiedClient=this.factory.CreateClient(url);
//// make http request with policy embedded already;
}
public SomeHttpService(IHttpClientFactory factory)
{
this.factory=factory;
}
}
In the DI container to create a singleton would the following be an ok way to do?
public void ConfigureServices(IServiceCollection services)
{
var botClient = new TelegramBotClient(_config["Tokens:Telegram"]);
services.AddSingleton(botClient);
}
TelegramBotClient is from the library that I'm using, so I can't start changing that.
A more orthadox way of handling a DI service with a configuration is to use the IOptions pattern. This way you aren't tightly coupling your startup object with the service. As it stands now, if your configuration changes, you have to modify your startup object.
A way to tackle this and keep your concerns separated, take a look at this:
TelegramBotClientService.cs
public interface ITelegramBotClientService
{
Task DoSomethingAsync();
}
public sealed class TelegramBotClientService : ITelegramBotClientService
{
private readonly TelegramConfigModel _config;
public TelegramBotClientService(IOptions<TelegramConfigModel> options)
{
_config = options.Value;
}
public Task DoSomethingAsync()
{
var token = _config.Token;
// ...
}
}
Startup.cs
// ...
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TelegramConfigModel>(
Configuration.GetSection("TelegramConfig"));
services.AddSingleton<ITelegramBotClientService, TelegramBotClientService>();
}
// ...
appsettings.json
{
"TelegramConfig": {
"Token": "12345"
}
}
TelegramConfigModel.cs
public sealed class TelegramConfigModel
{
public string Token { get; set; }
}
This hasn't been tested, so there may be a typo somewhere, but, now your concerns are separated. The DI pipeline is now doing the instantiation and also injecting your configurations.
A side note
I noticed you may be injecting a singleton to maintain a bot. I would highly suggest you use IHostedService or BackgroundService and inject using AddHostedService to maintain something like a bot.
I have asp.net core application. I want to use IOptions pattern to inject values from appsettings.json. So I have a class SecurityHeaderOptions, and also have target class SecurityHeadersBuilder whose constructor takes IOptions<SecurityHeaderOptions> as parameter.
I know that .net core can implicitly create instance of SecurityHeadersBuilder by injecting IOptions<SecurityHeaderOptions> after registering both with container.
However i want to explicitly create instance of SecurityHeadersBuilder, call one of its method and then register the instance with the container.
public sealed class SecurityHeaderOptions
{
public string FrameOption { get; set; }
public string XssProtection { get; set; }
}
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddConetntSecurityPolicy();
return this;
}
}
ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(?????).AddDefaultPolicy())
}
Questions
1> If i am explicitly passing options into constructor, do i need to register SecurityHeaderOptions with the container using service.Configure method?
2> Configuration.GetSection("SecurityHeaderOptions") can't return instance of IOptions<SecurityHeaderOptions> , instead it returns IConfigurationSection?
3>Either way, how do I retrieve and pass SecurityHeaderOptions into SecurityHeadersBuilder's constructor?
Using .NET Core 2 and not having a provider available (or caring to add it) in ConfigureServices I opted to go with something like this (using OP code as example):
public void ConfigureServices(IServiceCollection services)
{
// secOpts available for use in ConfigureServices
var secOpts = Configuration
.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
...
}
This is how I register options and inject into SecurityHeadersBuilder
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var option = provider.GetService<IOptions<SecurityHeaderOptions>>();
return new SecurityHeadersBuilder(option)
.AddDefaultPolicy();
});
}
Docs specifically say:
Don't use IOptions<TOptions> or IOptionsMonitor<TOptions> in Startup.ConfigureServices. An inconsistent options state may exist due to the ordering of service registrations.
So you'll have to access the configuration some other way from Startup.ConfigureServices, e.g. Quinton's answer
Firstly you need to add a second constructor to SecurityHeadersBuilder, that takes a plain SecurityHeaderOptions:
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder(SecurityHeaderOptions options)
{
_options = options;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then the answer entirely depends on whether or not you need to use those options outside of your Startup class.
If not, you can simply use the Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>() extension method:
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = Configuration.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});
(you can then delete the SecurityHeadersBuilder constructor that takes IOptions<SecurityHeaderOptions>).
If you will need to use these options elsewhere, then you can combine the above approach with the Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure() extension method:
var optionsSection = Configuration.GetSection("SecurityHeaderOptions");
services.Configure<SecurityHeaderOptions>(optionsSection);
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = optionsSection.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});
Regarding your questions:
1. Yes, you need to register the options, but I believe you are doing it the wrong way (at least by your example). You should register as this:
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
2. I believe that the correct registration I refer above returns what you are expecting.
3. Just registering it and placing it on the SecurityHeaderBuilder constructor is enough. You do not need (neither does the default .NET Core IOC container allows) to pass constructor parameters when registering it. For that you would need to use other IOC's such as Autofac.
But you need to register SecurityHeadersBuilder in order to use it within other classes.
Just use an interface for that.
public interface ISecurityHeadersBuilder
{
SecurityHeadersBuilder AddDefaultPolicy();
}
public class SecurityHeadersBuilder : ISecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then, just register it in startup.cs as this
services.AddScoped<ISecurityHeadersBuilder, SecurityHeadersBuilder>();
You could do something like this
public static class IConfigurationExtensions
{
public static TypedConfiguration<SecurityHeaderOptions> GetSecurityHeaderOptions(this IConfiguration configuration)
{
return new TypedConfiguration<SecurityHeaderOptions>(configuration.GetSection("SecurityHeaderOptions"));
}
}
public class TypedConfiguration<T> where T : class
{
public TypedConfiguration(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public T Value => Configuration.Get<T>();
public void InitializeOptions(IServiceCollection services)
{
services.Configure<T>(Configuration);
}
}
Now from single place you've created object that has both IConfiguration, typed SecurityHeaderOptions and helper method for registering IOptions injection for that class.
Use it like this
public void ConfigureServices(IServiceCollection services)
{
var wrappedOptions = Configuration.GetSecurityHeaderOptions();
wrappedOptions.InitializeOptions(services);
var options = Options.Create(wrappedOptions.Value);
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(options).AddDefaultPolicy());
}
I see a lot of code examples on how to use DI in .NET Core, however none of them use constructor parameters.
For example:
Create Authorization Service
Inject the current HTTP header(X-Api-Key) in constructor
In the implementation check if I have access
Here I need to not only use DI on my IAuthorizationService but also inject the token in the constructor. I know how to do it in Ninject, however have no experience in .NET Core DI.
Here is what I have as an example.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkSqlite();
services.AddDbContext<MainDbContext>();
services.AddScoped<IAuthorizationService, AuthorizationService>(); // Inject current HttpContext header value as a constructor?
}
I usually flow such values through a service where the data is set in a piece of middleware. For example:
An accessor class which can be injected:
public class ApiKeyAccessor
{
public string ApiKey { get; set; }
}
And a middleware which sets the API key at the beginning of the request:
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context, ApiKeyAccessor apiKeyAccessor)
{
StringValues key;
if (context.Request.Headers.TryGetValue("X-Api-Key", out key))
{
apiKeyAccessor.ApiKey = key;
return _next(context);
}
// todo: throw exception, etc..
}
}
Now all we have to is add the ApiKeyAccessor to the DI container with a scoped lifetime and add the ApiKeyMiddleware to the request execution pipeline, preferably as soon as possible.
When configured correctly, we can inject the ApiKeyAccessor instance in controllers or services:
public class AuthorizationService
{
private readonly string _apiKey;
public AuthorizationService(ApiKeyAccessor apiKeyAccessor)
{
_apiKey = apiKeyAccessor.ApiKey;
}
}