Inject AddSqlServer with another service - c#

Is it possible to resolve an instance of ISettingsService from the ConfigureServices method in Startup(.cs) - webapplication?
I've implemented a SettingsService which is able to retrieve the database connectionstring from an external source (secure credentials store). Within the ConfigureServices I need an instance of the ISettingsService in order to get the connectionstring and pass it to the services.AddSqlServer<MyDbContext>(connectionstring) method.
While creating the instance (using var provider = services.BuildServiceProvider(); var settings = provider.GetService<ISettingsProvider>();) Visual Studio displays the next error:
Another developer posted a similar question on StackOverflow and the answer provides a solution in case of AddSingleton/ AddTransient. What is the correct way to apply it on the AddSqlServer call? Or could you provide another solution to avoid the warning/ error message?

The Intellisense comment for .AddSqlServer actually says to use .AddDbContext if you need more control, and that's certainly the correct option.
If you refer to the source code here, you can see that all .AddSqlServer is actually doing is calling .AddDbContext and configuring the options accordingly. We can therefore write our own solution like this:
services.AddDbContext<DbContext>((serviceProvider, options) => {
var settings = serviceProvider.GetService<ISettingsProvider>();
// I don't know what methods your ISettingsProvider makes available
// so adjust accordingly
string connectionString = settings.GetSetting("connectionString");
options.UseSqlServer(connectionString);
});
Of course you can make other changes to the options here, and .UseSqlServer can also take a Action<SqlServerDbContextOptionsBuilder> (options.UseSqlServer(connectionString, opts => opts.EnableRetryOnFailure()), etc.) to further configure it.

Related

MAUI dependency injection service resolve in Program.cs

MAUI has dependency injection setup similar to what ASP.NET Core has in the Startup.cs class. This one is set in MauiProgram.cs file by default.
My question is: How can I get a service instance in this file after services registration? I guess, one solution will be the following, but then I must edit this code also if the constrctors of these services change during time:
var keyValueStore = new PreferencesKeyValueStore();
var accountService = new AccountService(keyValueStore);
var profileService = new ProfileService(keyValueStore);
builder.Services.AddSingleton<IKeyValueStore>(keyValueStore);
builder.Services.AddSingleton<IAccountService>(accountService);
builder.Services.AddSingleton<IProfileService>(profileService);
//Here now I can use accountService and profileService to do something
I can not find more elegant solution that will return the service instance for me from the DI container. Something like:
builder.Services.AddSingleton<IKeyValueStore, PreferencesKeyValueStore>();
builder.Services.AddSingleton<IAccountService, AccountService>;
builder.Services.AddSingleton<IProfileService, ProfileService>();
//Now I can't perform something like: var accountService = diContainer.GetInstance<IAccountService>(); or similar.
I don't know how to reach di container and ask it to provide me registered instance.
Actually, the documentation provided a simple way to do so.
Check it here
They recommended to use the Handler property of any object of type Element, there you can write the code :
// Considering you want to resolve a service from your custom interface IMyService
var service = this.Handler.MauiContext.Services.GetService<IMyService>();
// Then you can use the resolved service..
But there are some issues, personally it never worked for me, the Handler property may be null because of the lifecycle of the Element you are calling it on.
To avoid this issue, use a full line like:
var service = Application.Current.MainPage
.Handler
.MauiContext
.Services
.GetService<IMyService>();
// Then you can use the resolved service..
This works fine for me
Hope it helps you ..

C# AWS Parameter Store - Configuration not loading SystemManagerConfiguration in .Net 6

Application is not able to talk to AWS Parameter Store in .NET 6.
It is always talking to appsettings.json.
I tried debugging locally, still same behavior. Not able to find the SystemManagerConfiguration under list of configuration .
var builder = WebApplication.CreateBuilder();
var connectionString = builder.Configuration.GetConnectionString("OrderTrackerDatabase");
Packages Used
Library Source Code : https://github.com/aws/aws-dotnet-extensions-configuration
image
I got the same issue and finally resolved it.
The samples code in https://github.com/aws/aws-dotnet-extensions-configuration missed one line as below after called "AddSystemsManager" method in .Net 6.
builder.Services.Configure<Settings>(builder.Configuration.GetSection($"common:settings"));
After added above line, then I'm able to get the correct values from AWS Parameter Store when using the settings.
I've also created an issue of this in GitHub as below -
https://github.com/aws/aws-dotnet-extensions-configuration/issues/114
I believe the problem might be the trailing slash after "/OrderTracking/", try "/OrderTracking" instead.
WebApplication.CreateBuilder() will create new instance and doesn't carry over the SystemManager configuration.
Instead, use IConfiguration instance through constructor DI.
var connectionString = _configuration.GetConnectionString("OrderTrackerDatabase");
In my case this extensions method was returning null at my lambda:
private static IConfiguration InitializeConfiguration() => new ConfigurationBuilder()
.AddSystemsManager($"/my-data", true, TimeSpan.FromMinutes(5))
.Build();
Because the role of lambda didn't have permission for read SSM for that resource.
User: is not authorized to perform: ssm:GetParametersByPath on resource
So, just add necessary permission (ssm:GetParametersByPath) for the role of lambda to your resource at System Manager.
In my case, I am using lambda serverless, so the IConfig is always null when it is passed to the controller.
I resolved it by changing the IOptions<Settings> settings in the Controller constructor to IConfiguration settings and then access the parameters by name like _settings.GetValue<string>("ParameterName")
A little less "Object Oriented", but it seemed much easier than this complex solution

How to use a Service or DbContext inside DbCommandInterceptor?

I have an application that syncs data from a MySql database to a SQL Server database.
Considering those two DbContext services:
services.AddDbContext<SqlServerContext>(options => options
.UseSqlServer(Configuration.GetConnectionString("SqlServer")));
services.AddDbContext<MySqlContext>(options => options
.UseMySql(Configuration.GetConnectionString("MySql"))
.AddInterceptors(new MySqlInterceptor()));
In the MySqlInterceptor(); I want to inject/resolve/use a Service or even the SqlServerContext, in order to get configurations to modify the CommandText.
Any ideas?
Depending on the method you are going to override you will receive CommandEventData object in the method definition which has the DbContext as property.
As to the services and configurations you can configure the interceptor before registration.
Instead of this:
services.AddDbContext<MySqlContext>(options => options
.UseMySql(Configuration.GetConnectionString("MySql"))
.AddInterceptors(new MySqlInterceptor()));
you can do
var interceptor = new MySqlInterceptor(service1, service2 ... etc);
services.AddDbContext<MySqlContext>(options => options
.UseMySql(Configuration.GetConnectionString("MySql"))
.AddInterceptors(interceptor))
How to resolve the interceptor instance:
If you need to auto-wire the dependencies of the interceptor you can do the following
services.AddTransient<Service1>();
services.AddTransient<Service2>();
services.AddTransient<MySqlInterceptor>();
// resolve the instalce of the interceptor
var serviceProvider = services.BuildServiceProvider();
var interceptor = serviceProvider.GetService<MySqlInterceptor>();
// configure mysql context and interceptor
services.AddDbContext<MySqlContext>(options => options
.UseMySql(Configuration.GetConnectionString("MySql"))
.AddInterceptors(interceptor))
As #vasil mentioned in his answer:
Depending on the method you are going to override, you will receive
CommandEventData object in the method definition which has the
DbContext as property.
In my case though, I wanted to resolve a service that used another DbContext, which proved to be cumbersome; so instead I ended up putting the settings I needed into appsettings.json, and used IConfiguration service to get the setting value and sent it to the Interceptor constructor:
services.AddDbContext<MySqlContext>(options => options
.UseMySql(Configuration.GetConnectionString("MySql"))
.AddInterceptors(new MySqlInterceptor(Configuration["SettingValue"])));
Note: If you landed on this answer and was looking for a way to resolve a service inside the ConfigureService method, without calling BuildServiceProvider which, like David Fowler says on a Github issue, you'd be:
building the container while trying to build it
and you'll end up having:
2 containers and one of them will never be disposed.
You can do what Nkosi suggests in his answer:
services.AddScoped<IService>(x =>
new Service(x.GetRequiredService<IOtherService>(),
x.GetRequiredService<IAnotherOne>(),
""));

ASP.NET Core access service in Startup.cs ConfigureServices method

I need to access a service inside ConfigureServices method in Startup.cs and I do this:
services.AddScoped<ICustomService, CustomService>();
var sp = services.BuildServiceProvider();
var service = sp.GetService<ICustomService>(); // this is null
However var service above is always null.
What do i do wrong?
I had this sort of problem - I had a singleton 'settings' service which I wanted to use. I solved it by Actually creating one then registering that exact instance with DI via the overload that lets you specify a 'provider', rather than just registering the class, and adding a nice big comment explaining this:
var settingsService = new SettingsService(_hostingEnvironment);
//Add a concrete settings service which is then registered as the de facto settings service for all time.
//we need to do this as we want to use the settings in this method, and there isn't a satisfactory method to
//pull it back out of the IServiceCollection here (we could build a provider, but then that's not the same provider
//as would be build later... at least this way I have the exact class I'll be using.
services.AddSingleton<ISettingsService, SettingsService>((p) => settingsService);
..
..
..
var thing = settingsService.SomeSettingIWant();
If what you want isn't a singleton but is something transient, then I guess you can just create a concrete class for it right there? I know it probably feels a bit like cheating, but it would work fine...

Custom MembershipProvider Initialize method

When overriding the MembershipProvider and calling it directly, is there a way to fill the NameValueCollection config parameter of the Initialize method without manually looking through the config file for the settings?
Obviously this Initialize is being called by asp.net and the config is being filled somewhere. I have implemented my own MembershipProvider and it works fine through the build in controls.
I would like to create a new instance of my provider and make a call to it directly, but I don't really want to parse the .config for the MembershipProvider, it's connection string name and then the connection string if it's already being done somewhere.
tvanfosson- Thanks for the help. (if I had the 15 points necessary I would vote you up)
From your link I was able to figure it out. It turns out the second parameter to the Initialize proceedure was the list of parameters from the provider and could be reached in the following way:
string configPath = "~/web.config";
Configuration config = WebConfigurationManager.OpenWebConfiguration(configPath);
MembershipSection section = (MembershipSection)config.GetSection("system.web/membership");
ProviderSettingsCollection settings = section.Providers;
NameValueCollection membershipParams = settings[section.DefaultProvider].Parameters;
Initialize(section.DefaultProvider, membershipParams);
Not sure why you want to create a new one, but if you create it yourself, you'll need to read the web config and get the values yourself to pass to Initialize() as this is done outside the class. I'm sure, though, that there is already a section handler for this section so it should be just a matter of doing:
MembershipSection section = WebConfigurationManager.GetSection("membership");
Then find your provider and accessing its properties to construct the NameValueCollection. I don't think you will have to write any code to parse the configuration section.
Here is the MembershipSection documentation at MSDN. Drill down from there.
In any case you shouldn't create instance of MembershipProvider. It is creating and initializating by standard asp.net infrastructure. You can access to it by code like this one:
var customerMembership = Membership.Provider;

Categories