Search code examples
dependency-injectionasp.net-core-2.1class-library

DI parameters to class library without controller


So I'm not sure if I'm just missing something, but basically every example of DI I see with asp.net core shows passing of parameters from the appSettings.json file through a constructor in the controller and then to anything else.

Can I bypass the Controller and just inject directly a Class Library?

For an example of what I'm trying to do, let's say I have appSettings.json with

"EmailSettings":{"smtpServer":"mail.example.com", "port":123, "sendErrorsTo":"[email protected]"}

Then a Class Library for EmailServices

EmailSettings.cs

public class EmailSettings{
   public string smtpServer {get;set;}
   public int port {get;set;}
   public string sendErrorsTo {get;set;}
}

IEmailService.cs

public interface IEmailService
{
   void SendErrorEmail(string method, Exception ex);
}

and EmailService.cs

public class EmailService :IEmailService
{
   private readonly EmailSettings _emailSettings;
   public EmailService(EmailSettings emailSettings)
   {
      _emailSettings = emailSettings;
   }
   public void SendErrorEmail(string method, Exception ex)
   {
      ....
   }
}

Startup.cs in the main asp.net core application

public void ConfigureServices(IServiceCollection services)
{
   ...
   services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
   services.AddScoped<IEmailService, EmailService>(p => {
      return new EmailService(p.GetService<EmailSettings>());
   });
   ...
}

Without loading the EmailServices or the appsetting.json parameters through the controller and then into the BusinessLayer class library, I want to be able to call the SendErrorEmail from BusinessLayer (or any other place).

DoWork.cs

public MakeItWork()
{
   try
   {...}
   catch (exception ex)
   {
      IEmailService.SendErrorEmail("BAL - MakeItWork",ex)
   }
}

But it just fails with a null exception. The DI in the startup doesn't create the EmailService in place of the IEmailService, and I'm going to guess the parameters are not there either.

Thanks for any help you can give.

----EDIT----

I ended up just switching to using AutoFac for DI. It's able to accomplish what I was looking for. Accepted the answer below to give Phantom the points for trying to assist.


Solution

  • A couple of things:

    • In your MakeItWork() method, you have code that "calls" a method using the interface name - not even sure how that will compile. You need to use an object of a class that implements that interface to actually make method calls at runtime. For example, in your DoWork class, you could have a constructor requesting for an instance of a class that implements the IEmailService interface and store it for future use in other methods.

    • Second, in the Services collection, you are adding a "Scoped" dependency (in the ConfigureServices method). A "scoped" dependency is only created upon a (http)Request, typically via calls to controllers. From your code and your explanation, it looks like you are wanting to add a Singleton object for your IEmailService interface. So, instead of adding a Scoped dependency use AddSingleton - as you have done, you can also create the specific object in the call to AddSingleton - that means this object will be provided every time you request it (through class constructors, for example). If you are using it as a singleton, you should also make sure that it is thread safe. Alternatively, you can also add the dependency using AddTransient - if you use this, a new object will be created every time you request it.

    Update: Sample Code

    Modify your ConfigureServices to make the EmailService as Transient (this means a new object every time this service is requested):

    public void ConfigureServices(IServiceCollection services)
    {
       ...
       services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
       services.AddTransient<IEmailService, EmailService>();
       ...
    }
    

    Your "DoWork" class should request the EMail Service in the constructor:

    public class DoWork()
    {
    
    private IEmailService _emailService;
    
    //Dependency should be injected here
    public DoWork(IEmailService emailService)
    {
        _emailService = emailService;
    }
    
    
    public MakeItWork()
    {
       try
       {...}
       catch (exception ex)
       {
          //Use the saved email service object to do your work
          _emailService.SendErrorEmail("BAL - MakeItWork", ex)
       }
    }
    
    }
    

    It doesn't end here. The question remains as to how you are going to create an Object of the DoWork class. For this, one idea is to create an interface for the DoWork class itself and then setup the container for that interface as well. Then wherever you would want to use the DoWork implementation you can "request" the interface for DoWork. Or use the container directly to create an instance.