.NET Core Dependency Injection how to handle multiple objects - c#

As the title says I have a .NET Core application that I am trying to convert over to and take advantage of the built in Microsoft Dependency Injection.
I have an object and a base class for the object, call it CommunicationBase and Communicator. When my app starts up and reads the configuration file, I can have N number of objects to instantiate.
Previously, before switching to Dependency Injection, somewhere in my startup routine, where I read the configuration file, I would have a List<CommunicationBase> variable that I would instantiate and add Communicator objects to and at the same time, set some of the base properties, which changed based on how many were in my configuration and each ones properties in config.
How would I achieve this with DI?
I understand that in my services, I would register the type so it can be injected into other class constructors.
For example, services.AddTransient<CommunicationBase, Communicator>(); but as I understand it, this just registers the types with DI. I can inject it into a class and have a random instance of one of them.
How would I then have N number of instances and be able to set properties of each one as I create the instance?
Or, is this a scenario where DI is not necessary or won't work and I need to just do it the way I was doing it before?
Thanks!

I would slightly modify approach shown here. So I would define some enum that would then be used to decide what instance to return.
Sample classes setup and the enum:
public enum CommuniationType
{
False, True, Other,
}
public abstract class CommunicationBase
{
public CommunicationBase(CommuniationType communiationType)
{
CommuniationType = communiationType;
}
public bool IsConnected { get; set; }
public CommuniationType CommuniationType { get; protected set; }
}
public class Communicator : CommunicationBase
{
public Communicator(CommuniationType communiationType) : base(communiationType) { }
}
Now, in the place where you have access to service collection (e.g. in ASP.NET the place would be Stratup.RegisterServices method) you define your objects of concrete class and register them, as in the sample code below (at the bottom, there are also test classes using CommunicationBase object for testing puproses):
public class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
SetupNObjects(serviceCollection);
serviceCollection.AddTransient<CommunicationBaseServiceResolver>(serviceProvider => communicationType =>
{
var implementations = serviceProvider.GetServices<CommunicationBase>();
return implementations.First(x => x.CommuniationType == communicationType);
});
serviceCollection.AddScoped<FalseTestClass>();
serviceCollection.AddScoped<TrueTestClass>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var f = serviceProvider.GetService<FalseTestClass>();
var t = serviceProvider.GetService<TrueTestClass>();
}
// Here you should take care of registering objects, after reading config.
// That would be best place to do that.
static void SetupNObjects(ServiceCollection serviceCollection)
{
var comFalse = new Communicator(CommuniationType.False);
comFalse.IsConnected = false;
var comTrue = new Communicator(CommuniationType.True);
comTrue.IsConnected = true;
serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comFalse);
serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comTrue);
}
}
public class FalseTestClass
{
private readonly CommunicationBase communication;
public FalseTestClass(CommunicationBaseServiceResolver resolver)
{
communication = resolver(CommuniationType.False);
}
}
public class TrueTestClass
{
private readonly CommunicationBase communication;
public TrueTestClass(CommunicationBaseServiceResolver resolver)
{
communication = resolver(CommuniationType.True);
}
}

Firstly do you need to has clear the differences between Transient, Scoped, Singleton lifetime. To understand how works with the list of Communicator objects that will be read from your configuration file.
One approuch to resolve your question is
Create an interface ICommunicatorList with one method to get a List, i mean you can envolve the list of communicators.
Create a clase that inherits from ICommunicatorList (for example called CommunicatorList), with a private field for your list of Communicators. On the constructor method set your private field with the list of communicator, o here you can receive like a parameter from the section of the config file to iterate and full your private field.
on this class implement your code to return the list of communicators.
Now, in your startups file you can now create the service
services.AddTransient< ICommunicatorList>(x => new CommunicatorList(parameters));

I would do it the following way.
First you have communicators and settings classes:
namespace WebApiApp
{
public abstract class CommunicationBase
{
public abstract string Communicate();
}
public class Communicator1Settings
{
public string Parameter { get; set; }
}
public class Communicator1 : CommunicationBase
{
private readonly string parameter;
public Communicator1(string parameter)
{
this.parameter = parameter;
}
public override string Communicate()
{
return $"Type: {nameof(Communicator1)}, parameter: {this.parameter}";
}
}
public class Communicator2Settings
{
public string Parameter1 { get; set; }
public string Parameter2 { get; set; }
}
public class Communicator2 : CommunicationBase
{
private readonly string parameter1;
private readonly string parameter2;
public Communicator2(string parameter1, string parameter2)
{
this.parameter1 = parameter1;
this.parameter2 = parameter2;
}
public override string Communicate()
{
return $"Type: {nameof(Communicator1)}, parameter1: {this.parameter1}, parameter2: {this.parameter2}";
}
}
public class CommunicatorsSettings
{
public List<Communicator1Settings> Communicators1 { get; set; }
public List<Communicator2Settings> Communicators2 { get; set; }
}
}
In appsettings.json you have the configuration of communicators:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Communicators": {
"Communicators1": [
{
"Parameter": "First communicator1 parameter"
},
{
"Parameter": "Second communicator1 parameter"
}
],
"Communicators2": [
{
"Parameter1": "First communicator2 parameter1",
"Parameter2": "First communicator2 parameter2"
},
{
"Parameter1": "Second communicator2 parameter1",
"Parameter2": "Second communicator2 parameter2"
}
]
}
}
So you have two instances of Communicator1 with different parameters and two instances of Communicator2 with different parameters as well.
Then, you configure the container. The following is the content of program.cs for .net 6:
using WebApiApp;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
AddCommunicators();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
void AddCommunicators()
{
var settings = new CommunicatorsSettings();
builder.Configuration.Bind("Communicators", settings);
foreach (var communicatorSettings in settings.Communicators1)
{
builder.Services.AddScoped<CommunicationBase>(
_ => new Communicator1(communicatorSettings.Parameter));
}
foreach (var communicatorSettings in settings.Communicators2)
{
builder.Services.AddScoped<CommunicationBase>(
_ => new Communicator2(communicatorSettings.Parameter1, communicatorSettings.Parameter2));
}
}
Now you can inject IEnumerable<CommunicationBase> into your controller:
using Microsoft.AspNetCore.Mvc;
namespace WebApiApp.Controllers
{
[ApiController]
[Route("[controller]")]
public class CommunicatorsController : Controller
{
private readonly IEnumerable<CommunicationBase> communicators;
public CommunicatorsController(IEnumerable<CommunicationBase> communicators)
{
this.communicators = communicators;
}
public IActionResult Get()
{
var result = this.communicators.Select(x => x.Communicate());
return this.Json(result);
}
}
}
This is the result for /communicators web API:
[
"Type: Communicator1, parameter: First communicator1 parameter",
"Type: Communicator1, parameter: Second communicator1 parameter",
"Type: Communicator1, parameter1: First communicator2 parameter1, parameter2: First communicator2 parameter2",
"Type: Communicator1, parameter1: Second communicator2 parameter1, parameter2: Second communicator2 parameter2"
]

Related

C# Injecting Specific Item from appSettings.json at runtime

How can I inject a specific setting (of possibly many) from an array appSettings.json in a C# .NET Core Web API, based on a runtime input value?
appSettings.json:
{
"SettingProfiles": [
{
"Name": "Profile1",
"SettingA": "SettingAValue1",
"SettingB": "SettingBValue1"
},
{
"Name": "Profile2",
"SettingA": "SettingAValue2",
"SettingB": "SettingBValue2"
}
...
}
Settings Classes:
public class Settings {
public List<SettingsProfile> SettingsProfiles { get; set; }
}
public class SettingsProfile {
public string Name { get; set; };
public string SettingA { get; set; };
public string SettingB { get; set; };
}
Service class:
public class MyService : IMyService {
private readonly SettingsProfile _Profile;
public MyService(SettingsProfile profile) {
_Profile = profile;
}
public void DoStuff() {
Console.WriteLine($"Setting A: {_SettingsProfile.SettingA}, Setting B: {_SettingsProfile.SettingB}")
}
}
The user will enter the setting name they want to apply. I am unsure how to do this if the service is configured in Startup.cs, at which point I don't yet have the setting to use.
I am understanding that "newing" the service would be bad practice, although that's the only way I can figure out how to make it work:
public class MyController {
private readonly Settings _Settings;
public MyController(Settings settings) {
_Settings = settings;
}
public IActionResult DoStuff(profileName) {
SettingsProfile profile = _Settings.Where(profile => profile.Name == profileName);
MyService service = new Service(profile);
}
}
I'm obviously missing something, but I've been watching YouTube videos on Dependency Injections and reading StackOverflow until my eyes bleed, and haven't figured it out yet. Can someone help me with a pattern that I should be following?
This is how I think it should work.
It will be a lot cleaner if you use another pattern: Factory.
interface ISettingServiceFactory{
MyService GetService(string profileName);
}
class SettingServiceFactory: ISettingServiceFactory
{
MyService GetService(string profileName){
}
}
Now you can implement GetService in two ways.
The first one is by creating new as you did in the controller and is not that bad as this is the purpose of the factory. In this way you kind of move that logic somewhere else.
A second one would be a bit uglier but something like this
interface ISettingServiceFactory{
MyService GetService(string profileName);
void SetCurrentProfile(SettingsProfile profile);
}
class SettingServiceFactory: ISettingServiceFactory
{
private IServiceProvider _serviceProvider;
private Settings _Settings;
public SettingServiceFactory(IServiceProvider serviceProvider,Settings settings){
_serviceProvider = serviceProvider;
_Settings = settings;
}
MyService GetService(string profileName){
var service = _serviceProvider.GetRequiredService<MyService>();
var profile = _Settings.Where(profile => profile.Name == profileName);
service.SetCurrentProfile(profile);
return service;
}
}
This second approach would be useful only if the implementation of MyService has a lot of other dependencies by itself and if you want to avoid new at any cost.
In both cases you will inject the factory in the controller
public MyController(ISettingServiceFactory settingServiceFactory) {
_settingServiceFactory= settingServiceFactory;
}
public IActionResult DoStuff(profileName) {
MyService service = _settingServiceFactory.GetService(profileName)
}

How can I populated an IEnumerable<MyType>() on an object via configuration?

I'm trying to populate the following object using the Option pattern:
public class MyParentPolicyOptions
{
public IEnumerable<SomePolicySettings> SomePolicySettings { get; set; }
}
My config json looks like this:
{
"MyParentPolicy": {
"SomePolicies": [
{
"Name": "Default",
"SomeSetting": 3,
"IsHappy": false
},
{
"Name": "Custom",
"SomeSetting": 5,
"IsHappy": true
}
]
}
For the configuration I do something like this:
serviceConfigurationDefinition.Optional("MyParentPolicy", Validators.MyParentPolicyOptions());
At the point of building the configuration builder I can see it has my properties as expected in the following pattern:
{[MyParentPolicy:SomePolicies:0:Name, Default}]
{[MyParentPolicy:SomePolicies:0:SomeSetting, 3}]
{[MyParentPolicy:SomePolicies:0:IsHappy, false}]
However, after applying this configuration root to the ServiceConfigurationDefinition my actual MyParentPolicyOptions.SomePolicySettings is still null. It seems to work for other strongly typed objects but I can't get it to work for Lists / IEnumerables / arrays etc.
Just to add, I've just tried this with Dictionary<int, SomePolicySetting> in the hope that the automatic indexing would mean this was actually a dictionary type, but didn't work.
I don't know for the serviceConfigurationDefinition.Optional() method you use. In general I do it this way. You're right IEnumerable is working. The issue is somewhere else in your code. The following example is working.
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices( (context,services) =>
{
services.AddOptions();
services.Configure<MyParentPolicy>(context.Configuration.GetSection("MyParentPolicy"));
services.AddHostedService<Worker>();
})
.Build();
await host.RunAsync();
public class MyParentPolicy
{
public IEnumerable<SomePolicySettings> SomePolicies { get; set; }
}
public class SomePolicySettings
{
public string Name { get; set; }
public string SomeSetting { get; set; }
public bool IsHappy { get; set; }
}
and appsettings.json:
{
"MyParentPolicy": {
"SomePolicies": [
{
"Name": "Default",
"SomeSetting": 3,
"IsHappy": false
},
{
"Name": "Custom",
"SomeSetting": 5,
"IsHappy": true
}
]
}
}
And finally retrieve the options with IOptionsMonitor<MyParentPolicy> for example:
public Worker(ILogger<Worker> logger, IOptionsMonitor<MyParentPolicy> options)
{
_logger = logger;
_parentPolicyOptions = options.CurrentValue;
}
The easiest way is to get it from startup configuration
List<SomePolicySettings> settings= configuration.GetSection("MyParentPolicy")
.Get<MyParentPolicy>().SomePolicies.ToList();
if you want to use it everywhere inside of your app , you can create a service
startup
services.Configure<MyParentPolicy>(configuration.GetSection("MyParentPolicy"));
services.AddSingleton<SomePolicySettingsService>();
service class
public class SomePolicySettingsService
{
private readonly List<SomePolicySettings> _somePolicySettings;
public SomePolicySettingsService(IOptions<MyParentPolicy> myParentPolicy)
{
_somePolicySettings = myParentPolicy.Value.SomePolicies;
}
public SomePolicySettings GetDefaultPolicySettings()
{
return _somePolicySettings.FirstOrDefault(i=>i.Name=="Default");
}
public SomePolicySettings GetCustomPolicySettings()
{
return _somePolicySettings.FirstOrDefault(i => i.Name == "Custom");
}
}
or instead of creating and injecting service, you can just inject
" IOptions myParentPolicy" in the constructor everywhere you need.

Swagger - Inject string into IOperationFilter

I am having an OperationFilter which needs a string to work
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public AuthorizeCheckOperationFilter(string scope)
{
Scope = scope;
}
public string Scope { get; }
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Here is the Scope needed
}
}
and the Swaggergen in Startup:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc(apiVersion, new OpenApiInfo
{
Title = apiName,
Version = apiVersion,
});
...
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
The Problem is, that Swagger cannot resolve the AuthorizeCheckOperationFilter because there is no string registered
I also tried to register the AuthorizeCheckOperationFilter with a given String, but this also won't work.
Is there a way to achieve this?
When adding your filter, you'll need to pass the parameter as well like this:
options.OperationFilter<AuthorizeCheckOperationFilter>("myScope");
Tested an verified on my computer.
Another solution is to wrap the string in some object. For example, if the string needs to be configurable, you could wrap it in an options object like this. (If not, you could just as easily create a POCO to hold your string, and register it with the DI container.)
appsettings.json
{
"AuthorizationOptions": {
"Scope": "myScope"
}
}
AuthorizationOptions.cs
public class AuthorizationOptions
{
public string Scope { get; set; }
}
Startup.cs
services.Configure<AuthorizationOptions>(Configuration.GetSection(nameof(AuthorizationOptions)));
AuthorizeCheckOperationFilter.cs
public class AuthorizeCheckOperationFilter : IOperationFilter
{
private readonly string scope;
public AuthorizeCheckOperationFilter(IOptions<AuthorizationOptions> authorizationOptions)
{
this.scope = authorizationOptions.Value.Scope;
}
// rest of your implementation...
}

.NET Core Configuration is forcing me to DependencyInjection

I want to read appsettings.json non-controller class.Consider has a DatabaseUtil and contain a static connect() method. I need to connectionString for connection and i'm getting this from appsettings.json.This operation piece of cake in the startup.cs:)
Like this:
Configuration.GetConnectionString("HangfireDBConn")
Also it can be at the controller side with dependcy injection.But my problem which want to reach appSettings from DatbaseUtil class.
appSettings.json:
"NotifySettings": {
"DbConnection": "abc",
"Email": "abc#domain.com",
"SMTPPort": "5605"
}
Then i created my configuration settings class:
public class NotifySettings
{
public string DbConnection { get; set; }
public string Email { get; set; }
public string SMTPPort { get; set; }
}
And I added dependency for constructor injection to DatabaseUtil class and added IDatabaseUtil
public class DatabaseUtil : IDatabaseUtil
{
private static NotifySettings _NotifySettings;
public DatabaseUtil(IConfiguration _iconfig)
{
_NotifySettings = _iconfig.GetSection("NotifySettings").Get<NotifySettings>();
}
public static String ConnectToDatabase()
{
return "MESSAGE :" + _NotifySettings.DbConnection;
}
}
}
And i added DatabaseUtil to startup.cs
services.AddScoped<IDatabaseUtil, DatabaseUtil>();
and finally i injected IDatabaseUtil to my controller class and i can reach mysettings end of the this work.
Yes i can but not best way!
Let the join my Brain Storming :) ; If i have to inject to IDatabaseUtil every class where i want to use db helper methods.But if i had a static method in this class just it need to this line of code:
DatabaseUtils.connect();
That's feels me like i wrote unnecessary code.
What do you think about my approximation.Which one is best way for this case ?
change
services.AddScoped<IDatabaseUtil, DatabaseUtil>();
to
services.AddSingleton<IDatabaseUtil, DatabaseUtil>();
This way you only have one instance of DatabaseUtil
I'm still not entirely clear, but if the need here is to make values from your Configuration statically available, then copy them from your configuration to a static class during the startup:
public static class GlobalSettings
{
public static string ConnectionString { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
GlobalSettings.ConnectionString = Configuration.GetSection("ConnectionString").Value;
// ...
}
}
If you need to get the config and do the assignment from somewhere else, use the ConfigurationBuilder:
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
using System;
using Microsoft.Extensions.Configuration;
namespace project.Utility
{
public class ConnectionString
{
private IConfigurationRoot _config;
private static ConnectionString _internalInstance;
public static ConnectionString Instance
{
get
{
return _internalInstance;
}
}
public static void Init(IConfigurationRoot config)
{
_internalInstance = new ConnectionString();
_internalInstance._config = config;
}
public String Get(string key)
{
var NotifySettings =
Instance._config.GetSection(key).Get<NotifySettings>();;
return NotifySettings;
}
}
}
// call this above method from any place like controller or class file by below code
// use refernece of the namespace
ConnectionString connectionString = new ConnectionString(); // object creation
NotifySettings settings = connectionString.Get("NotifySettings"); // call with your key value get the settings object
Try this it should work let me know if any issues i can help on that

What's the correct way of implementing a data access manager with support for multiple connections so that it can be injected?

I am developing a WebAPI in .Net Core 2.2. I need a custom Data Access object to query data. However, I need different connections for different tasks. What's the best approach for this using DI and .NetCore IOptions pattern?
I have tried having all connections in the DAL object and specifying the connection id when invoking the method. I have the option of having different classes for each one, but I guess that's overkill.
So let's suppose I have this into appsettings.json
"DatabaseMetaConfiguration": {
"MappingDb": {
"DNS": "pgc.app.corp.acme.com",
"Database": "mappings",
"User": "mappings_user",
"Pass": "111",
"SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
},
"StatusDb": {
"DNS": "pgc.app.corp.acme.com",
"Database": "status",
"User": "status_user",
"Pass": "222",
"SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
},
"MasterDb": {
"DNS": "pgc.app.corp.acme.com",
"Database": "master",
"User": "master_user",
"Pass": "333",
"SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
},
"BookDb": {
"DNS": "pgc.app-books.corp.acme.com",
"Database": "book",
"User": "book_user",
"Pass": "444",
"SSL": "SSL Mode=Prefer; Trust Server Certificate=true"
}
}
Then the POCO options classes:
public class PostgreSqlDatabaseMetaConfigurations : Dictionary<string, PostgreSqlDatabaseMetaConfiguration> { }
public class PostgreSqlDatabaseMetaConfiguration
{
public string Database { get; set; }
public string User { get; set; }
public string Pass { get; set; }
public string DNS { get; set; }
public string SSL { get; set; }
public int Timeout { get; set; }
public int CommandTimeout { get; set; }
public string ConnectionString { get { [...] } }
}
And the actual DAL class:
public class PostgreSqlManager : IDisposable, ISqlManager
{
private readonly Dictionary<string, NpgsqlConnection> _connections;
private readonly ILogger<PostgreSqlManager> _logger;
public PostgreSqlManager(IOptionsMonitor<PostgreSqlDatabaseMetaConfigurations> optionsAccessor, ILogger<PostgreSqlManager> logger)
{
_logger = logger;
var dbConfigurations = optionsAccessor.CurrentValue;
if (dbConfigurations == null || !dbConfigurations.Any())
_logger.LogWarning("No connections configured!");
// Create a dictionary of connections, indexed by the connection names
_connections = dbConfigurations.ToDictionary(c => c.Key, c => new NpgsqlConnection(c.Value.ConnectionString));
}
// So, a sample method would be...
public async Task<long> ExecuteScalarAsync(string connectionId, string commandText, List<DbParameter> parameters = null, CommandType commandType = CommandType.Text)
{
using (var command = GetDbCommand(_connections[connectionId], commandText, parameters, commandType))
{
try
{
await command.Connection.OpenAsync();
return (Int64)await command.ExecuteScalarAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception on ExecuteScalarAsync({0})", commandText);
throw;
}
finally
{
command.CloseCommand();
}
}
}
}
Finally, in ConfigureServices I have this lines:
services.AddSingleton<ISqlManager, PostgreSqlManager>();
services.Configure<PostgreSqlDatabaseMetaConfigurations>(configuration.GetSection("DatabaseMetaConfiguration"));
(Sorry for the big chunks of code, I wanted it to be clear.)
Now, if I want to make use of the DAL service class, I need to inject an ISqlManager in my controller. Then, on each method call, I need to specify which connection I want.
Now here is my question. If I know beforehand which connection I need in each controller class, how can I specify it so that each controller uses the connection I know it will need?
Better yet, can I setup several different instances of the DAL class into DI container, each one with a different connection, and let each controller class decide which one to use?
Thanks for reading.
I would suggest using AddScoped to register your PostgreSqlManager. This will keep the object alive for the duration of the web request.
You could then do something like
public interface IBookRepository
{
Book GetByISBN(string isbn);
Add(Book book)
Delete(Book book)
}
public class BookRepository: IBookRepository
{
private ISqlManager _sqlManager;
public BookRepository(ISqlManager sqlManager)
{
_sqlManager = sqlManager;
}
public Book GetByISBN(string isbn)
{
var rows = _sqlManager.GetRows("BooksDb", "exec uspGetBookByISBN", CreateStringParam(isbn));
// convert rows into book object(s)
return books.FirstOrDefault();
}
}
Now you can register one or more repository per database and register e.g.
services.AddScoped();
And in your controller inject the ones you need.
public class MyController: Controller
{
private IBookRepository _bookRepository;
private IStatusRepository _statusRepository;
public MyController(IBookRepository bookRepository, IStatusRepository statusRepository)
{
_bookRepository = bookRepository;
_statusRepository = statusRepository
}
}
The DI container will make sure that your dependencies are resolved correctly as long as they have been registered.

Categories

Resources