Reputation: 1378
In a .NetCore application I want to inject into a service a setting that has some custom logic in the constructor. In particular, I have a setting section looking similar to this:
"Email": {
"FromEmailName" : "My name",
"FromEmail" : "[email protected]"
}
Now, I'd like to bind this to a class looking like:
public class EmailSettings {
public System.Net.Mail.MailAddress MailAddress { get; }
public EmailSetting(string fromEmailName, string fromEmail) {
MailAddress = new MailAddress(fromEmail, fromEmailName);
}
}
From the docs I see that the EmailSettings
class must have a parameterless constructor, but is there any way that I can overcome this problem by introducing an 'intermediate' class UnparsedEmailSetting
, and then do something like the code below?
public static void BindEmailConfig(this IServiceCollection services, IConfiguration configuration) {
var section = configuration.GetSection("Email");
services.Configure<UnparsedEmailSettings>(section);
var emailConfig = section.Get<UnparsedEmailSettings>();
var emailSettings = new
EmailSettings(emailConfig.FromEmailName, emailConfig.FromEmail);
services.Configure<EmailSettings>(emailSetting); // does not work
services.RemoveConfiguration<UnparsedEmailSettings); // method does not exist
}
Upvotes: 4
Views: 3450
Reputation: 10851
First, the answer from Jakob will work fine, but it won't give you the services that IOptions
can provide (such as reloading). If that is important and the information in your question is complete you should be able to do a work-around. Otherwise I strongly suggest Jakobs approach.
Keep the settings:
"Email": {
"FromEmailName": "My name",
"FromEmail": "[email protected]"
}
Register your config like so:
services.Configure<EmailSettings>(Configuration.GetSection("Email"));
And change your model:
public class EmailSettings
{
public string FromEmailName { get; init; }
public string FromEmail { get; init; }
public System.Net.Mail.MailAddress MailAddress => new MailAddress(FromEmail, FromEmailName);
}
That way you don't need any special logic in your constructor. In C#10 C#11 you should be able to add required
to the property as well.
public required string FromEmail { get; init; }
The downside is that your new EmailSettings
will expose more members than necessary. But it will still be immutable.
If it's important to also not expose any other members you can simply add an interface:
public interface IEmailSettings
{
System.Net.Mail.MailAddress MailAddress { get; }
}
And change your registration like so:
services.Configure<EmailSettings>(Configuration.GetSection("Email"));
services.AddSingleton<IOptions<IEmailSettings>>(sp =>
sp.GetRequiredService<IOptions<EmailSettings>>());
Now you can inject IOptions<IEmailSettings>
. Or you can change the registration to IOptionsMonitor
if you want to enable reloading.:
services.AddSingleton<IOptionsMonitor<IEmailSettings>>(sp =>
{
return sp.GetRequiredService<IOptionsMonitor<EmailSettings>>();
});
Upvotes: 3
Reputation: 1216
Since adding an option basically means adding a singleton of type IOptions<TOptions>
, you can do that. There is no constructor for Options, but you can use the create method from the Options extensions.
services.AddSingleton<IOptions<EmailSetting>>(serviceProvider =>
{
return Options.Create(new EmailSetting(Configuration.GetSection("Email").GetSection("FromEmailName").Value,
Configuration.GetSection("Email").GetSection("FromEmail").Value));
});
It can then be injected as usual with IOptions<EmailSetting>
.
Upvotes: 4