7

I'm trying to inject a couple email accounts that I have in appsettings.json into an email service.

EDIT: My EmailRepository.cs needs a DbContextinjected as well. I used @Nkosi 's answer, which worked without needing a DbContext. I'm planning on using DbContextPool in production, so how do I pull one of those out in my ConfigureServices method?

appsettings.json:

"SanSenders": [
  {
    "Host": "mail.theFourSeasons.com",
    "FromName": "The Four Seasons",
    "IsNoReply": true,
    "IsSssl": true,
    "Password": "tooGoodToBeTrue",
    "Port": 465,
    "Username": "[email protected]"
  },

  {
    "Host": "mail.theFourSeasons.com",
    "FromName": "Franki",
    "IsNoReply": false,
    "IsSssl": true,
    "Password": "cantTakeMyEyesOffYou",
    "Port": 465,
    "Username": "[email protected]"
  }
]

SanSender.cs:

public class SanSender
{
    public string FromName { get; set; }
    public string Host { get; set; }
    public bool IsNoReply { get; set; }
    public bool IsSsl { get; set; }
    public string Password { get; set; }
    public int Port { get; set; }
    public string Username { get; set; }

    public async Task<bool> SendEmailAsync(
        string toAddress, string subject, string htmlMessage)
    {
        throw new NotImplementedException();
    }
}

EmailRepository.cs:

public class EmailRepository
{
    public IEnumerable<SanSender> SanSenders { get; set; }

    //Edit: I need a DbContext injected as well.
    public EmailRepository(ApplicationDbContext applicationDbContext,  
        IEnumerable<SanSender> sanSenders)
    {
         SanSenders = sanSenders;
    }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<List<SanSender>>((settings) =>
    {
        Configuration.GetSection("SanSenders").Bind(settings);
    });

    services.AddScoped<EmailRepository>();

    services.AddControllersWithViews();
}

The Controller:

public class HomeController : Controller
{
    public HomeController(
        IOptions<List<SanSender>> optionsSanSenders, EmailRepository emailService)
    {
    }

    public IActionResult Index()
    {
        return View();
    }
}

In the controller I added IOptions<List<SanSender>> optionsSanSenders and I can get access to them there, but EmailService.cs is in a class library and I would prefer to not add unnecessary dependencies to it.

The EmailService does have IEnumerable<SanSender> SanSenders, but it has a zero length/count.

What am I doing wrong?

2 Answers 2

7

Change the approach.

public class SanSenderOptions {
     public List<SanSender> SanSenders { get; set; }
}

Extract the settings from configuration and then register them where needed

public void ConfigureServices(IServiceCollection services) {
    SanSender[] senders = Configuration.GetSection("SanSenders").Get<SanSender[]>();

    services.Configure<SanSenderOptions>(options => {
        options.SanSenders = senders.ToList();
    });

    services.AddScoped<EmailRepository>(sp => 
        new EmailRepository(sp.GetRequiredService<ApplicationDbContext>(), senders));

    services.AddControllersWithViews();
}

The repository will get the senders injected being resolved.

The controller should be refactored to get the configured options.

public class HomeController : Controller {
    public HomeController(IOptions<SanSenderOptions>> optionsSanSenders, 
          EmailRepository emailService) {
        var senders = options.Value.SanSenders; //<--
    }

    public IActionResult Index() {
        return View();
    }

}

Reference Configuration in ASP.NET Core

Reference Options pattern in ASP.NET Core

Sign up to request clarification or add additional context in comments.

5 Comments

Ugh, simplified my code for easier reading. Problem is, my EmailRepository needs a DbContext injected as well. And I'll be using DbContextPool in production, so don't want to new up an instance in configure services. Gonna update my question.
Ok, I've updated my question to include needing a DbContext
@MichaelTranchida updated answer. I also assume you registered the DbContext since it was not shown in the simplified example you provided.
Thank you, was hunting around trying to find that.
Will this approach respect reloadOnChange for the json file?
2

Introducing an additional configuration level as mentioned in answer of Nkosi is not needed (anymore?) in .NET 6.

You can use IOptions<List<SanSender>> to inject and fetch the configuration in classes.

Additionally I would recommend to use binding to tell how the configuration is fetched like

services.Configure<List<SanSender>>(
    options => Configuration.Bind("SanSenders", options));

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.