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;
}
Related
I move an application to .net core and trying to use IOptions pattern.
My application is multi tenant with single database. System has global default options and I keep them in database (same as my old application) and also each tenant has own options. If tenant has no option with a key in global, so I need to use global option.
In configuration, I handle to getting global options from database. It is easy with example in documentation.
However, each tenant options not going well. Although I actually know what I want, I don't know how to do it in .Net Core.
I test in a console application.
class Program {
static void Main(string[] args) {
var services = ConfigureServices();
var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetService<App>().Run();
}
public class App {
private readonly IOptionsSnapshot<DemoOptions> _options;
public App(IOptionsSnapshot<DemoOptions> options) {
_options = options;
}
public void Run() {
Console.WriteLine("Hello from App.cs");
Console.WriteLine($"DemoOptions:Global:Enabled={_options.Value.Enabled}");
Console.WriteLine($"DemoOptions:Global:AutoRetryDelay={_options.Value.AutoRetryDelay}");
Console.WriteLine($"DemoOptions:Global:IdentityOptions:MaxUserNameLength={_options.Value.IdentityOptions.MaxUserNameLength}");
}
}
private static IServiceCollection ConfigureServices() {
IServiceCollection services = new ServiceCollection();
//Load global configuration from database and use them.
var config = LoadConfiguration();
services.AddSingleton(config);
services.AddDbContext<EntityConfigurationContext>(options => options.UseInMemoryDatabase("InMemoryDb"));
services.AddScoped<ITenantService, TenantService>();
//I take this part from example link in below. But I am not successed.
services.AddSingleton<IOptionsMonitorCache<DemoOptions>,TenantOptionsCache<DemoOptions>>();
services.AddTransient<IOptionsFactory<DemoOptions>,TenantOptionsFactory<DemoOptions>>();
services.AddScoped<IOptionsSnapshot<DemoOptions>,TenantOptions<DemoOptions>>();
services.AddSingleton<IOptions<DemoOptions>,TenantOptions<DemoOptions>>();
// required to run the application
services.AddTransient<App>();
return services;
}
public static IConfiguration LoadConfiguration() {
var builder = new ConfigurationBuilder();
builder.Sources.Clear();
builder.AddEntityConfiguration(options => options.UseInMemoryDatabase("InMemoryDb"));
IConfigurationRoot configurationRoot = builder.Build();
DemoOptions options = new();
configurationRoot.GetSection($"{nameof(DemoOptions)}:{DemoOptions.Global}").Bind(options);
Console.WriteLine($"DemoOptions:Global:Enabled={options.Enabled}");
Console.WriteLine($"DemoOptions:Global:AutoRetryDelay={options.AutoRetryDelay}");
Console.WriteLine($"DemoOptions:Global:IdentityOptions:MaxUserNameLength={options.IdentityOptions.MaxUserNameLength}");
return builder.Build();
}
}
public record DemoOptions {
public const string Global = nameof(Global);
public const string Tenant = nameof(Tenant);
public bool Enabled { get; set; }
public TimeSpan AutoRetryDelay { get; set; }
public IdentityOptions IdentityOptions { get; set; }
}
public record IdentityOptions {
public int MaxUserNameLength { get; set; }
}
public record DemoSettings(string Key, string Value) {
public int Id { get; set; }
}
public record TenantSettings(string Key, string Value, int TenantId) {
public int Id { get; set; }
}
I add some important class here. However if you want to look at all project, I add github link.
I use this example
I see.
You ought to do these things
install a nuget package
Microsoft.Extensions.Options.ConfigurationExtensions
In your Programs.cs in ConfigureServices replace this code
var config = LoadConfiguration();
services.Configure<DemoOptions>(config.GetSection($"{nameof(DemoOptions)}:{DemoOptions.Global}"));
services.AddSingleton(config);
You can try ready-to-use JsonRepositoryConfiguration Nuget package. It also has a auto-refresh feature and works pretty much like configuration form JSON config files, only it expects the JSON config from your repository which can in turn bring the data from any internal/external storage including making a database call, API call etc.
You can mix different configuration provides (like this one and JSON config files) and use them together should you like to. Confession: I am the author.
The next step would be using Named Options.
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>();
//
...
...
}
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.
In .NET Core, if my appsettings file looks like
{
"Section": {
"Field": "value"
}
}
I can create a class like
public class Section
{
public string Field { get; set; }
}
and retrieve the value in Startup like
public void ConfigureServices(IServiceCollection services) {
services.Configure<Section>(this.Configuration.GetSection("Section"));
}
The problem is that if for some reason (say misspelling) the binding fails, it is not going to throw, and instead it will create a Section object with null (default) value for the Field property.
Is there a way to make services.Configure<Section>(this.Configuration.GetSection("Section")); to throw if the binding fails?
I am just summing up #Nkosi's answer here which makes this validation possible using data annotation.
1- Annotate the properties of your class:
public class Section
{
[Required]
public string Field { get; set; }
}
2- Create an extension method to enable validation to take effect:
public static class ConfigurationModelValidation
{
public static T GetValid<T>(this IConfiguration configuration)
{
var obj = configuration.Get<T>();
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
}
3- In the Startup class, register you configuration models as below using GetValid method (instead of using 'IOptions'):
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton(this.Configuration.GetSection("Section").GetValid<Section>());
}
4- Now in the user's class directly inject your configuration model:
public class MyClass
{
private readonly string field;
public MyClass(Section section)
{
this.field = section.field;
}
}
Now if binding fails for any reason, the validation will kick in and it will throw, enjoy!
You can just get the section first, then verify it exists (because it will not be null).
public void ConfigureServices(IServiceCollection services) {
var section = this.Configuration.GetSection(nameof(Section));
if (!section.Exists()) throw new Exception();
services.Configure<Section>(section);
}
Hi i am new to DI with unity. I am developing a custom Attribute. In that attribute I want to inject a dependency with help of Unity. But when use of that attribute in a class it shows exception. The code is:
public interface ITest
{
}
public class AAttrib : Attribute
{
ITest _test;
public AAttrib(ITest test)
{
_test = test;
}
}
public class Test : ITest
{
}
[AAttrib]
public class Example
{
// to do
}
the exception is:
There is no argument given that corresponds to the required formal
parameter 'test' of 'AAttrib.AAttrib(ITest)
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<ITest, Test>(new HierarchicalLifetimeManager());
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
the Unity resolver class is:
public class UnityResolver: IDependencyResolver
{
protected IUnityContainer _container;
public UnityResolver(IUnityContainer container)
{
if(container == null)
throw new ArgumentNullException("container");
this._container = container;
}
public void Dispose()
{
_container.Dispose();
}
public object GetService(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch (ResolutionFailedException r)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return _container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = _container.CreateChildContainer();
return new UnityResolver(child);
}
}
You cannot use Dependency Injection on Attributes, cause Attributes are Metainformation that extend the meta information of classes. And these meta information are generated during compile time
It is indeed not possible to use DI in attributes. There is however a nice clean workaround by using a decorator.
See this blog: https://blogs.cuttingedge.it/steven/posts/2014/dependency-injection-in-attributes-dont-do-it/
I tried this and I've been using it for quite some time now. You will be able to do exactly what you need with this.
(Blog has .net framework & .net core solutions)
Apologies for late entry.
Not sure if unity can let you do that. But PostSharp has a way of achieving this. I've never tried this as PostSharp wasn't an approved DI framework in my project. But I liked the solution.
Auto data contract
This doesn't answer your question but gives a different perspective to the solution that you are thinking.
You should avoid attributes. Attributes are good starting point but get in the way of application extension.
Attributes are hardcoding and is violation of at least two SOLID principles, Single Responsibility and Open/Closed principles. It should rather be done using Fluent APIs
I would rather replace AAttrib with a Fluent API.
Attribute Example
public class Person {
[StringLength(100)]
[RegularExpression("^([a-zA-Z0-9 .&'-]+)$", ErrorMessage = "Invalid First Name")]
public string FirstName { get; set; }
}
FluentValidation Example:
public class Person {
public string FirstName { get; set; }
}
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleFor(x => x.FirstName).NotNull().WithMessage("Can't be null");
RuleFor(x => x.FirstName).Length(1, 100).WithMessage("Too short or long");
RuleFor(x => x.FirstName).Matches("^([a-zA-Z0-9 .&'-]+)$").WithMessage("Invalid First Name"));
}
}
This will essentially do the same thing that attribute is doing. With the right DI wiring in place. This can introduce great flexibility. Read other articles on wiring attributes with members.