Implementing the Options pattern with a class injected during ConfigureServices - AddScoped - c#

I have a small class to obtain a series of information about my user on several of my MVC applications. A minimal reproducible example would be:
public class InformationGetter
{
public string GetUserInformation(string connectionStr, string storedProcedureName, int userId)
{
// Do SQL work
return info;
}
}
I'm injecting it on the ConfigureServices step using
services.AddScoped<InformationGetter>
And then in my classes I simply call it from the DI.
Now, obviously the connectionStr and storedProcedure only changes per application but right now I'm passing it as parameter.
I've tried to make those parameters public and configure it using services.Configure but when I call it from my controllers, I get null values.
services.AddOptions();
services.Configure<InformationGetter>(options =>
{
options.ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
options.StoredProcedureName = "prInformationGetter";
});
I'm not sure if the reason why this is failing it's because I'm missing an interface on my original class or am I failing to understand this concept.
I've also thought on doing something like services.AddInformationGetter(options => {}) but my understanding is that this pattern is to implement middlewares and not DI specifically.
I tried checking the documentation (learn.microsoft.com) but I got even more confused.

There may be misunderstanding of the concepts involved.
Configure<TOption> will register IOptions<TOptions>. There are now two separate registrations in your example.
Once when you register the class
services.AddScoped<InformationGetter>()
and the other when you register the options.
Do the following
//..
services.AddOptions();
//Adds IOptions<InformationGetter>
services.Configure<InformationGetter>(options => {
options.ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
options.StoredProcedureName = "prInformationGetter";
});
//Adds InformationGetter but gets it from the registered options
services.AddScoped<InformationGetter>(sp =>
sp.GetRequiredService<IOptions<InformationGetter>>().Value
);
//...
The scoped registration will use the factory delegate to extract the options registered and return the desired type.
public class InformationGetter {
public string ConnectionString { get; set; }
public string StoredProcedureName { get; set; }
//...
public string GetUserInformation(int userId) {
// Do SQL work
return info;
}
}
InformationGetter looks like a service.
I would suggest refactoring to follow a more Single Responsibility Principle (SRP) and Separation of Concerns (Soc) design.
//Needed by InformationGetter to perform its function
public class InformationGetterOptions {
public string ConnectionString { get; set; }
public string StoredProcedureName { get; set; }
}
//abstraction of InformationGetter
public interface IInformationGetter {
string GetUserInformation(int userId);
}
//implementation.
public class InformationGetter : IInformationGetter{
private readonly InformationGetterOptions options;
public InformationGetter(InformationGetterOptions options) {
this.options = options;
}
public string GetUserInformation(int userId) {
//use values in options to connect
// Do SQL work
return info;
}
}
I would have avoid options pattern altogether and just registered the class using the delegate factory, extracting what I need from configuration. That way your code is not tightly coupled to framework concerns like IOptions
public void ConfigureServices(IServiceCollection services) {
//...
InformationGetterOptions options = new InformationGetterOptions {
ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
StoredProcedureName = "prInformationGetter";
};
services.AddSingleton(options);
services.AddScoped<IInformationGetter, InformationGetter>();
//...
}
Now IInformationGetter can be injected where needed and have all the necessary dependencies to perform its function.

Related

Blazor FluentValidation how to Inject dependency to AbstractValidator

I am using FluentValidator for a current Blazor-Server Project.
Now i need to inject my database service class for validate duplication and stuff like that.
public class StockValidator : AbstractValidator<LagertypModel>
{
private StockOverviewService _stockservice;
public StockValidator(StockOverviewService stockservice)
{
_stockservice = stockservice;
RuleFor(LagertypModel => LagertypModel.Lagertyp).NotEmpty().MaximumLength(4).Must(Lagertyp => {
return _stockservice.validateStockTypeCU(Lagertyp).Result;
});
}
}
But when i do that i get the error that StockValidator needs to have a parameterless contructor.
How can i inject my dependency now?
Where you instantiate your StockValidator class make sure you pass your service into it like so:
Program.cs
builder.Services.AddSingleton<StockOverviewService>();
StockOverviewService.cs
private StockValidator_validator;
public StockOverviewService(LiveConnectionString connectionString)
{
_connectionString = connectionString;
_validator = new StockValidator(this);
}
StockValidator.cs
public class StockValidator : AbstractValidator<LagertypModel>
{
private StockOverviewService _stockservice;
public StockValidator(StockOverviewService stockservice)
{
_stockservice = stockservice;
RuleFor(LagertypModel => LagertypModel.Lagertyp).NotEmpty().MaximumLength(4).Must(Lagertyp => {
return _stockservice.validateStockTypeCU(Lagertyp).Result;
});
}
}
Assuming your StockOverviewService uses async methods for accessing a DB/Api, I don't recommend this approach because you're making the code run synchronously here:
return _stockservice.validateStockTypeCU(Lagertyp).Result;
but it will work.

Dependency injection between two ASP.NET Core projects

I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}

What's the correct flow of injecting Appsettings.json from a main application and how to do it

I have my main .net core application called AppOne. In its appsettings.json I define which api's it should be able to call. For example:
"ApiSettings": {
"UrlToCall": "http://test",
}
Then there is my intermediate and shared project library, called InfraApp that makes the call itself to the Api.
There might be a second app called AppTwo where the url is different.
Both AppOne and AppTwo reference the InfraApp since the logic is common and call the code in there to make the actual call. However the settings (that specifies which url to call) are specific to the api's themselves and therefore cannot be specified in the InfraApp.
Let's consider only AppOne so far.
Such settings are registered through the Options pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.0) into the startup.cs:
services.AddOptions();
var apiSettings = Configuration.GetSection("ApiSettings");
services.Configure<ApiSettings>(apiSettings);
and I have my ApiSettings class:
public class ApiSettings
{
public string UrlToCall { get; set; }
}
what is the correct way to pass such ApiSettings to the InfraApp ? InfraApp doesn't know anything about ApiSettings since this is defined in the AppOne. Should I defined the ApiSettings class into the InfraApp? IMHO sounds wrong because it is something specific about the AppOne api but maybe I am thinking in the wrong way. Thanks!
I think, if you have something like this:
public interface IApiSettings
{
string UrlToCall { get; set; }
}
public class ApiSettings:IApiSettings
{
public string UrlToCall { get; set; }
public ApiSettings()
{
...
Console.WriteLine($"ApiSettings");
...
}
}
public class IInfraApp{}
public class InfraApp : IInfraApp
{
private IApiSettings _ApiSettings;
//using Microsoft.Extensions.Options:
public InfraApp(IOptions<ApiSettings> settings)
{
_ApiSettings = (IApiSettings)settings.Value;
Console.WriteLine($"InfraApp {_ApiSettings.UrlToCall}");
}
}
then, you can add/register, something along these lines:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
...
...
//
services.Configure<ApiSettings>(Configuration);
services.AddTransient<IInfraApp, InfraApp>();
...
...
//get instance of infraapp:
var provider = services.BuildServiceProvider();
var infraapp = provider.GetService<IInfraApp>();
//
...
...
}

How should I pass parameters to the Autofac container for auto dependency resolution via Autofac.Dependency injection lib

Here is the problem. I'm currently using Autofac to resolve all the dependencies with TypedParameters in my AspNetCore MVC app, but I think I'm do something wrong and I can do it cleaner.
Below are following code samples
Configuration for the services.
Sample repository to inject
Current method used
What i want to do
Configuration:
public static void Configure(IConfiguration cfg,IServiceCollection services)
{
/// some code is skipped here. Module registrant is just pulling out
/// the services from dlls and register them.
ioCBuilder.Populate(services);
ioCBuilder.RegisterModule(new ModuleRegistrant(cfg, registrantOptions));
IoCHelper.Container = ioCBuilder.Build();
}
Sample Repository:
public class PriorityRepository: IPriorityRepository
{
public PriorityRepository(DbContext db)
{
Db = db;
}
/// <inheritdoc />
public Priority GetDefault()
{
return Db.Set<Priority>().SingleOrDefault(it => it.IsDefault);
}
}
Currently I get Repository with following:
public class PriorityController: Controller
{
public PriorityController(TestContext db)
{
var ctxParam = new TypedParameter(typeof(DbContext), db);
PriorityRepository = IoCHelper.Container.Resolve<IPriorityRepository>(ctxParam);
}
public IPriorityRepository PriorityRepository { get; set;}
}
I want it to be something like that
public class PriorityController: Controller
{
public PriorityController(IPriorityRepository priorityRepo)
{
PriorityRepository = priorityRepo;
}
public IPriorityRepository PriorityRepository { get; set;}
}
So basically the question is: How do I inject the already registered types which has slightly different type(more abstract) in the constructor?
The Func is used to resolve parameterized dependencies in Autofac.
Kindly go through the link https://autofaccn.readthedocs.io/en/latest/resolve/relationships.html#parameterized-instantiation-func-x-y-b for implementation details and other available options.

C# Pass parameter to method using lambda expression

Anyone that used ASP.Net might have seen methods that accept an Action<T> delegate that is used to configure some options. Take this one as an example in a ASP.Net Core MVC Startup.cs.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication().AddJwtBearer(options => {
options.Audience = "something";
// ...
});
//...
}
The .AddJwtBearer() method takes an Action<T> delegate with the configured parameters inside the lambda expression, what I'm trying to do is replicate that in one of my projects, but with no luck.
I got the Method(Action<T> action) part down, but I can't seem to retrieve the resulting object that was configured when the method was called.
Some code for context:
The builder class method:
public ReporterBuilder SetEmail(Action<EmailConfig> config)
{
if (config is null)
throw new ArgumentNullException();
// Get configured EmailConfig somehow...
return this;
}
The EmailConfig model:
public class EmailConfig
{
public EmailProvider EmailProvider { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string FromAddress { get; set; }
public string ToAddress { get; set; }
}
So what I'm trying to achieve here is this:
Reporter reporter = new ReporterBuilder()
.SetEmail(config => {
config.EmailProvider = EmailProvider.Other;
config.Host = "something";
// ...
})
.Build();
I looked at some Microsoft repositories on GitHub (aspnet/DependencyInjection, aspnet/Options seen to be the two main ones that use this approach with their IoC container) to see how they do to grab values from an Action<T> delegate, with absolutely no luck.
A few hours search on the internets didn't help either, as most articles are outdated or had nothing to do with what I'm trying to do.
Any help on how I can make this work is very welcome, also suggestions on better ways this can be done are also welcome.
Your method needs to instantiate the object, then call the method, passing the object into the method. A Func<T> would return an object. This is ActionT<T>, which doesn't return anything. Instead, it accepts the object.
public ReporterBuilder SetEmail(Action<EmailConfig> config)
{
if (config == null)
throw new ArgumentNullException();
var cfg = new EmailConfig();
// optionally populate the cfg with
// default configuration before calling method
config(cfg);
// cfg contains your configuration
// and is full of that thing called 'love'
return this;
}

Categories

Resources