How to Implement DbContextOptionsBuilder in .NET Core - c#

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.

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 can I add a dbContext after Startup in .Net Core?

I am using .Net Core, using the built-in dependency injection. In my login screen, I need the user to also choose a departmental database - we have different databases with the same structure to use the same application with different data. However, I can't figure out how to add/modify the dbContext that late. Startup.cs has the DI, but I don't know which connection string to read from the config until the user has chosen the department. It is a small database, and the company is not concerned about the management of the duplicate databases.
How can I add the service late
services.AddDbContext<my_accountingContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("CorrectDepartmentConfig")));
when I actually know what CorrectDepartmentConfig is?
Or, if that can't be done, how can I do a smelly change of the my_accountingContext after Startup.cs?
You can use an implementation factory overload of IServiceCollection in ConfigureServices method form Startup class:
//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());
The implementation of CreateApplicationDbContext depends on your specific needs, but a base implementation should look like the following:
public ApplicationDbContext CreateApplicationDbContext(){
//TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
}
After this implementation, you can inject the correct ApplicationDbContext in your controller, action...
public MyController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IActionResult([FromServices] ApplicationDbContext dbContext){
}
You can always set the connection string from inside the protected OnConfiguring method. You can get access to the IConfiguration instance from there (the DbContext class has a service locator, Instance property), retrieve the connection string, and then call UseMySql extension method with the appropriate connection.
Something like this:
protected virtual void OnConfiguring(DbContextOptionsBuilder builder)
{
var configuration = (this as IInfrastructure<IServiceProvider>).GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString("<name>");
builder.UseMySql(connectionString);
base.OnConfiguring(builder);
}
For the strongly-typed version of GetService do not forget to reference namespace Microsoft.Extensions.DependencyInjection.

Switch database using routes

Here's my scenario:
I Have a single app, but I need to switch the database connection by route.
Example:
switch(route)
{
case(URL/A):
{
USE DATABASE 1
}
case(URL/B):
{
USE DATABASE 2
}
DEFAULT:
USE DATABASE DEFAULT
}
Is it possible?
Since you're using ASP.NET MVC, your routes depends on your controllers. Then you can imagine having ControllerA using DatabaseA and ControllerB using DatabaseB.
To use multiple database connections, you need a connection string for each one of them.
I would use the following pieces of code to inject instances of DbContextOptionsBuilder inside of Startup.ConfigureServices()
var ContextAOptionsBuilder = new DbContextOptionsBuilder<ContextA>();
var ContextBOptionsBuilder = new DbContextOptionsBuilder<ContextB>();
Then you can configure your builders this way (depending on your parameters)
ContextAOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("ContextAConnectionString"), builder =>
{
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null);
});
ContextAOptionsBuilder.EnableSensitiveDataLogging();
Then you can inject them as singletons this way :
services.AddSingleton(typeof(DbContextOptionsBuilder<ContextA>),ContextAOptionsBuilder);
You can use a BaseController, whose constructor parameters can access to services this way :
public BaseController(IConfiguration configuration, IMemoryCache memoryCache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder){}
Of course, ControllerA and ControllerB being heir classes of BaseController, you can access desired builder quite simply.
public ControllerA(IConfiguration configuration,
IMemoryCache cache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder)
:base(configuration, cache, contextAccessor, ContextAOptionsBuilder,ContextBOptionsBuilder)
{
//Create your DbContext using the builder
}
This way you can use one, the other, or both database to build your context
A simpler way would have been injecting your configuration file and building your context from it's content but ppumkin's comment suggested it's a bad idea to do this at a controller level.
This solution is working for me in an ASP.NET Core MVC application, I am still learning the framework but maybe my answer gave you precisions about multiple DbContexts.
You can create 3 connection string also 3 data access Classes. First of your class uses for example DropCreateDatabaseIfModelChanges others use CreateDatabaseIfNotExists. When you call first class your database creates when you need others there will no need recreate it.
Register your context (as scoped, per request) and use factory method for dynamically creating context with specified connection string based on current route (which should be available from HttpContext or something similar). If the databases schemas are same and just data is different this should work easily. I can't provide a snippet for you because it's mostly depends on what DI framework you have.

Simple Injector and default AccountContoller dependency issue

I have problem with Simple Injector in my Web Api project. I user default AccountController generated by VS.
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
In my configuration file I register:
var container = new Container();
// This is an extension method from the integration package.
container.RegisterWebApiFilterProvider(config);
container.RegisterWebApiControllers(config);
container.Register<IInitializeService, InitializeService>();
container.Register<IFolderRepository, FolderRepository>();
container.Register<IUserRepository, UserRepository>();
container.Register<ILogger, Logger>();
//Authentication Wrap
container.Register<IUserStore<User, Guid>, ApplicationUserStore>();
container.Register<IDataSerializer<AuthenticationTicket>, TicketSerializer>();
container.Register<ISecureDataFormat<AuthenticationTicket>,
SecureDataFormat<AuthenticationTicket>>();
container.Register<IDataProtector>(
() => new DpapiDataProtectionProvider().Create("ASP.NET Identity"));
container.Verify();
// 4. Register the container as MVC3 IDependencyResolver.
DependencyResolver.SetResolver(new SimpleInjectorWebApiDependencyResolver(container));
config.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
I though that Simple Injector will be smart enough to handle all build in dependences responsible for default identity and i wont need to register them manually, but I'm getting exceptions if I wont register them.
Still getting an exception:
The constructor of type SecureDataFormat contains the parameter of type ITextEncoder with name 'encoder' that is not registered. Please ensure ITextEncoder is registered in the container, or change the constructor of SecureDataFormat.
Is there any way to handle that automatically?
I implemented a Web Api and I wrote this code.
This works for me
container.RegisterWebApiRequest<ISecureDataFormat<AuthenticationTicket>, SecureDataFormat<AuthenticationTicket>>();
container.RegisterWebApiRequest<ITextEncoder, Base64UrlTextEncoder>();
container.RegisterWebApiRequest<IDataSerializer<AuthenticationTicket>, TicketSerializer>();
container.RegisterWebApiRequest<IDataProtector>(() => new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider().Create("ASP.NET Identity"));
How is the container supposed to know which implementation of ITextEncoder you want to use in the constructor of SecureDataFormat?
You have to tell it which one to use. I think the rules basically go something like this:
if an interface is required (by a constructor) then it needs to know which implementation to use. If a concrete class is required it will automatically build an instance of that class (assuming it can resolve all the types that class needs).
As your SecureDataForms needs an interface you have to register one, otherwise its only option would be to 'guess' at which implementation you want and this could then go wrong silently if more than one implementation existed.
I just came up against this issue. I'm using ninject but you'll get the idea. Here is my binding:
kernel.Bind<ITextEncoder>().To<Base64UrlTextEncoder>();
According to the source, the only thing I ever see being used to new up an instance of SecureDataFormat is Base64UrlTextEncoder. So it seemed like a safe bet to use, but it's certainly not clear to me at first glance how to appropriately use this constructor overload.

Categories

Resources