11
votes

This seems like it should be really simple, I have been searching SO and a lot of other places for an answer to this, everything I have found and tried does not work.

I have an appsettings.json file that looks like this

"Email": {
"Port": "25",
"Host": "localhost",
"EnableSSL": "false",
"Credentials": {
  "Username": "fakeuser",
  "Password": "fakepassword"
},
"SystemFromAddress": "[email protected]",
"SystemFromDisplayName": "Test Sender",
"EmailTemplateRootDirectory": "Email\\EmailTemplates",
"EmailTemplates": [
  {
    "TemplateKey": "ResetPassword",
    "TemplatePath": "ResetPassword.cshtml"
  },
  {
    "TemplateKey": "NewAccount",
    "TemplatePath": "NewAccount.cshtml"
  },
  {
    "TemplateKey": "VerifyEmail",
    "TemplatePath": "VerifyEmail.cshtml"
  }
]

}

There are several models (EmailOptions being the parent) that I am trying to bind to, the EmailOptions class is expecting to have it's EmailTemplates list populated from the EmailTemplates list in the appsettings.json as seen above.

The parent class is being populated by the appsettings.json file as expected, the Child List of Email Templates in this class is always coming up empty.

Here are the classes I am binding to.

public class EmailOptions
{
    public int Port { get; set; }
    public string Host { get; set; }
    public bool EnableSSL { get; set; }
    public EmailCredentials Credentials { get; set; }
    public string SystemFromAddress { get; set; }
    public string SystemFromDisplayName { get; set; }
    public string EmailTemplateRootDirectory { get; set; }

    public IEnumerable<EmailTemplate> EmailTemplates { get; set; } = new List<EmailTemplate>();

}

public class EmailTemplate
{
    public string TemplateKey { get; set; }
    public string TemplatePath { get; set; }
}

public class EmailCredentials
{
    public string Username { get; set; }
    public string Password { get; set; }
}

I am using the following call I am making in my startup class in ASP.NET Core.

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddOptions();
        services.Configure<EmailOptions>( _configuration.GetSection("Email" ));
        ...

For some reason the IEnumerable property in my EmailOptions is not being deserialized from the appsettings.json into my options - when I attempt to use it anywhere in my controllers - the list is always set to an empty array.

FWIW: I have this working in a console application where I have more control over setting up my options from the appsettings.json. Here is what I am doing in the console app, (I am leaving out the code where I set up the options with the DI container for brevity)

var emailSection = configuration.GetSection( "Email" );
var emailOptions = emailSection.Get<EmailOptions>();

emailOptions.EmailTemplates = configuration.GetSection( "Email:EmailTemplates" ).Get<List<EmailTemplate>>();

as expected - in the console application, I get my Email Templates because i have the ability to get the child list separately and add it to the options before handing it over to the DI container. I don't seem to have that flexibility in the ASP.NET Core IServiceCollection.Configure() extension method (so maybe use another method to do this? which one? After a couple hours of searching I am crying uncle and asking for help).

So how does one get this to work using the ASP.NET Core "IServiceCollection.Configure()" method? Is there a better way to do this?

1
IEnumerable is an interface that could be implemented by multiple types and the deserializer can't guess. You should change it to a List then the deserializer will know how to deserialize it.Joe Audette
That was it. So simple and I missed it. Thank you so much for the help!Chase Thomas

1 Answers

15
votes

Thank you Joe for pointing out what needed to happen!

I made the false assumption that the serializer would happily create it's list from the json and assign that list to my IEnumerable - rather - you need to make sure to use List if you intend to deserialize a list of json objects into your Options (and other concrete dotnet types where applicable).

so instead of this

IEnumerable<EmailTemplate> EmailTemplates { get; set; }    

I should have had this...

List<EmailTemplate> EmailTemplates { get; set; }