Inject DataProtection based on injected user options IOptions<> in ConfigureServices - c#

I'm a little bit confused here regarding how to inject DataProtection in ConfigureServices(IServiceCollection services) based on user settings being as well injected from services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));.
The values appName_from_appsettings_json and dirInfo_from_appsettings_json below should be coming from the injected UserSettingsConfig and would be accessible anywhere else by injecting IOptions<UserSettingsConfig> but not here.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(appName_from_appsettings_json)
.PersistKeysToFileSystem(dirInfo_from_appsettings_json);
}
I've found ways to achieve my goals without using DI with code like var sharedDataProtectionAppName = configuration.GetValue<string>("UserSettings:SharedDataProtection:ApplicationName");
I had the feeling I have found the solution in this article http://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ by it seems like I can't figure out how to apply it to my case. I would need a way to inject DataProtection based on values from the injected IOptions<UserSettingsConfig>. What would be the cleanest way to do that in your opinion?
UPDATE: I found a solution based on that type of code that I could be calling from ConfigureServices, but I still wonder if it's the best way.
var userSettingsConfig = services.BuildServiceProvider().GetServices<IOptions<UserSettingsConfig>>().First();

You could also use the extension method .Bind(). This method will try to bind the value to the Configuration object by matching the keys from the configuration.
// Add framework services.
var userSettingsConfig = new UserSettingsConfig();
Configuration.GetSection("UserSettings").Bind(userSettingsConfig);
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(userSettingsConfig.appName)
.PersistKeysToFileSystem(userSettingsConfig.DirInfo);

Related

How can Autofac and an Options pattern be used in a .NET 5 console application?

I am trying to use an option pattern with Autofac and every attempt has just resulted in errors.
What I've tried:
Using the ConfigurationBuilder to retrieve an IConfiguration/IConfigurationRoot.
Register an instance of TestSectionOptions using the IConfiguration/IConfigurationRoot that was created before:
builder.Register(c => config.GetSection("TestSection").Get<TestSectionOptions>());
Trying to inject it via constructor injection:
private readonly TestSectionOptions _options;
public DemoClass(IOptions<TestSectionOptions> options)
{
_options = options.Value;
}
I'm getting following error:
DependencyResolutionException: None of the constructors found with
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type
'DemoApp.DemoClass' can be invoked with the available services and parameters:
Cannot resolve parameter
'Microsoft.Extensions.Options.IOptions1[DemoApp.TestSectionOptions] options' of constructor 'Void .ctor(Microsoft.Extensions.Options.IOptions1
Of course I tried other types of registration, but none of them worked.
I also know that I can simply bind the configuration file to a class, which I then register and inject without the IOptions<> part. But that would no longer correspond exactly to the option pattern, would it?
Even if it doesn't make a big difference, I'd still like to know why it doesn't work and how I could get it to work.
The problem is that this IOptions type should be registerd somewhere.
You can see e.g. this article. There is an example
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
So, somewhere inside Configure extension method it registers types for options, among others IOptions<>.
So, in your case you either have to do this explicitly, like
builder.Register(c => Options.Create(config.GetSection("TestSection").Get<TestSectionOptions>()))
This will register IOptions
or, you can create an empty service collection, then call Configure method on it, and then copy all registrations to autofac builder - there is Populate method from the package "Autofac.Extensions.DependencyInjection"
https://autofac.org/apidoc/html/B3162450.htm

How to configure IdentityOptions outside ConfigureServices?

I want to configure ASP.NET Core Identity based on settings which resides in database rather than AppSetting.json or hard coded values. Consequently, I'am eager to call following line of code outside of method ConfigureServices(IServiceCollection services):
services.Configure<IdentityOptions>(x => x.Password.RequireDigit = true);
This way of calling will allow me to initialize DbContext before trying to configure Identity.
Currently I'm using services.BuildServiceProvider() in the ConfigureServices() to access database values. This style has a huge disadvantage for me: It puts an extra initialization on the application's DbContext which is dirty and slow. In the other hand, DbContext is instantiated two times instead of one.
If I was able to call services.Configure<IdentityOptions>() outside the ConfigureServices(), for example in the configure() method, I would be able to configure Identity options based on database values without initializing DbContext twice.
Again, my question is how to configure IdentityOptions outside ConfigureServices?
Any help is appreciated.
I ended up with injecting IOptions<IdentityOptions> options to the Configure() method as what follows:
public virtual void Configure(IApplicationBuilder app, IOptions<IdentityOptions> options)
{
options.Value.Password.RequireDigit = true;
//rest of configurations...
}
And it worked!
Thanks to #Kirk for the link.

How to Implement DbContextOptionsBuilder in .NET Core

I am new to ASP.Net Core and I am trying to implement ASP.NET Core DI.
I configured like below in ConfigureServices Method in Startup.cs
services.AddScoped<DbContext, AutomationDbContext>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IUserService, UserService>();
In UserService Constructor, I am trying to use DI. I think below is NOT the right way to implement this.
public UserService(IHttpContextAccessor httpContextAccessor, AutomationDbContext automationDbContext, IConfiguration configuration)
{
this.configuration = configuration;
this.optionsBuilder = new DbContextOptionsBuilder<AutomationDbContext>();
var connectionString = this.configuration.GetConnectionString("Automation");
this.optionsBuilder.UseSqlServer(connectionString);
this.automationDbContext = new AutomationDbContext(this.optionsBuilder.Options);
this.httpContext = httpContextAccessor.HttpContext;
}
I don't like building optionsbuilder in constructor and get connectionstring.
What would be the better place to build these optionsBuilder and pass in constructor.
You need to use services.AddDbContext<TContext> instead:
services.AddDbContext<AutomationDbContext>(o =>
o.UseSqlServer(Configuration.GetConnectionString("Automation")));
Then, just inject your context:
public UserService(IHttpContextAccessor httpContextAccessor, AutomationDbContext automationDbContext)
As for IHttpContextAccessor, you should simply use:
services.AddHttpContextAccessor();
However, I would encourage you to strongly consider whether you actually need this in your service or not. If you need something like the current user's id, that should be passed into the method that needs it, not retrieved from within your service.
UPDATE
Since it was brought up, let me elucidate the reasons why adding your context in the way you currently are is incorrect, since it will shed a little light on how DI works in general.
First, you're binding DbContext directly to AutomationDbContext, which means you can then only use that one context. Maybe you don't need more than one context... now. That could change later. Second, when you register a service in that way, you can only inject the abstract type, i.e. DbContext here. The service registration literally means "when you see DbContext, inject an instance of AutomationDbContext". If you try to inject AutomationDbContext directly, as you're currently doing in your controller, that will actually throw an exception because that type is not actually registered as service: DbContext is. Third, AddScoped provides no real ability to configure the context, which is of course the part your were missing. There's ways to work around this such as using the factory overload of AddScoped or defining OnConfiguring on your context, but both of those are substandard to just using the right method in the first place: AddDbContext<TContext>
For what it's worth, there's also somewhat of a fourth reason, in that you can opt to use AddDbContextPool<TContext> instead of AddDbContext<TContext>, for connection pooling. There's no other way to set that up, so if you did want/need connection pooling, you'll never get there with AddScoped.

.NET Core with Autofac DI

I want to integrate Autofac to my API. Solution is split on several projects so that everything stays decoupled. I have set up my configure services like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
...
...
// Autofac
var builder = new ContainerBuilder();
builder.RegisterType<RouteRepository>().As<IRouteRepository>();
builder.Populate(services);
ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
However now with this code integrated, my API won't start anymore. If I start it in debug mode, I get no errors, but I don't get response either.
API landing route is pretty straightforward:
public IActionResult GetIndex()
{
return Ok("You are seeing this because controller is working!");
}
Also, what might be connected to the problem is that RouteRepository takes one variable as an argument in the constructor and I don't know where can I define what will be passed through? There is no config file by default.
If you're saying that you have one dependency for your RouteRepository, then you have to notify Autofac container how to resolve that:
// singletone
builder.RegisterInstance(new TaskRepository())
.As<ITaskRepository>();
// or instance based creation
builder.Register(c => new LogManager(DateTime.Now))
.As<ILogger>();
Or Autofac couldn't resolve your type.

.net-core Dependency Injection

I have a Generic repository which I want to register for DI, it implements an interface IRepository.
Normally I would create an instance of it like this:
IRepository repo = new Repository<Order>();
However I am trying to get up to speed in .net 5 ahead of release and want to get this working with DI, I have resorted to the following :
services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();
But this feels wrong, I don't want 50+ lines in there one for each of the classes in my model...
I cannot find anything online about this, I know its possible with other ioc containers.. but as this is a learning project I dont want to use another container, Im aiming to do it all with .net5s native container.
You should be able to register the open generic with
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
After some back and forwards in the comments to other answers I have a working solution, It might not be the best way but it works. Ill update again if I find a better way to implement this.
The two issues I had were : Needed to register a generic interface, the issue here was a lapse in concentration on my part.. I had the syntax wrong for registering a generic type which of course is :
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
The second issue was that I have an assembly which contains 50+ different models which I wanted registered, The way that I addressed this was to write a method that I can pass a list of assemblies to along with the Namespace that I want to register and it iterates over any types that match the criteria and registers them in the DI container.
public void RegisterModels(IServiceCollection services, string[] Assemblies, string #NameSpace)
{
foreach (var a in Assemblies)
{
Assembly loadedAss = Assembly.Load(a);
var q = from t in loadedAss.GetTypes()
where t.IsClass && !t.Name.Contains("<") && t.Namespace.EndsWith(#NameSpace)
select t;
foreach (var t in q.ToList())
{
Type.GetType(t.Name);
services.AddTransient(Type.GetType(t.FullName), Type.GetType(t.FullName));
}
}
}
This is then called from the startup.cs method ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TestContext>(options =>
options.UseSqlServer(#"Server=LOCALHOST\SQLEXPRESS;Database=Test;Trusted_Connection=True;"));
services.AddMvc();
RegisterModels(services, new string[] { "UI" }, "UI.Models");
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
}
There may be a better way to do this, there definitely is using different DI containers, if anyone has improvements to offer please let me know.
You could use a convention based registration library like Scrutor.
Scrutor is a small open source library that provides a fluent API to register services in your Microsoft.Extensions.DependencyInjection container based on conventions (Similar to Autofac's RegisterAssemblyTypes method, StructureMap's Scan method and Ninject's Conventions package).
This will allow you to do something like this:
services.Scan(scan => scan
.FromAssemblies(<<TYPE>>.GetTypeInfo().Assembly)
.AddClasses(classes => classes.Where(x => {
var allInterfaces = x.GetInterfaces();
return
allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(IRepository<>)));
}))
.AsSelf()
.WithTransientLifetime()
);
What you can do is create an extension method to encapsulate all those individual items that need to be registered.
That is the same technique Microsoft is using, for example you only put this in startup:
services.AddMvc();
but that is an extension method and behind the scenes you can bet it is registering a bunch of stuff it needs.
so you can create your own extension method like this:
using Microsoft.Extensions.DependencyInjection;
public static IServiceCollection AddMyFoo(this IServiceCollection services)
{
services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();
//....
return services;
}
and by making the method return the IServiceCollection you make it fluent so you can do
services.AddMyFoo().AddSomeOtherFoo();
Updated based on comment
the other technique to reduce registrations is when your dependency doesn't itself have dependencies you can make the constructor have a default of null so you still have decoupling and could pass a different one in later but the DI won't throw an error and you can just instantiate what you need if it is not passed in.
public class MyFoo(IFooItemDependency myItem = null)
{
private IFooItemDependency internalItem;
public MyFoo(IFooItemDependency myItem = null)
{
internalItem = myItem ?? new FooItemItem();
}
}
I'm not 100% sure on what your question is I assume you don't want to have
services.AddTransient<DAL.IRepository<Models.Order>, DAL.Repository<Models.Order>>();
services.AddTransient<DAL.IRepository<Models.Person>, DAL.Repository<Models.Person>>();
services.AddTransient<DAL.IRepository<Models.Invoice>, DAL.Repository<Models.Invoice>>();
etc
I have done this before (with ninject)
Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InRequestScope();
I imagine for Unity you can do something similar like
services.AddTransient<DAL.IRepository<>, typeof(Repository<>)();
And then to use it in a service
public OrderService(IRepository<Models.Order> orderRepository)
{
this.orderRepository = orderRepository;
}
EDIT
As pointed out by OP the correct syntax is:
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));

Categories

Resources