Search code examples
c#asp.net-coredependency-injectionserviceioc-container

In ASP.NET Core how do you get a scoped service instance in a non-controller class?


In an ASP.NET Core 3.1 website I am attempting to use constructor dependency injection in a class that is not a controller and it doesn't seem to work as expected. The MyService.cs doesn't seem to get the class instance and throws an error... which requires me to pass it from the controller instead of just doing dependency injection in the constructor as expected.

Some notes to clarify: The MyService.cs is a collection of Business Rules and not a service I am trying to inject (aka a business service). All I am trying to do is access the DataAccessLayerInterface DataAccessLayerService instance that was declared in the Startup.cs. The problem is when I try to use dependency injection in the constructor it doesn't work and throws a compile error. If I do the same exact thing in a controller though it works which baffles me.

Eventually I plan to build this out as a data layer but for now I am just passing a single string with the intent of building it out later. Below are the relevant parts I have been working on. For brevity I stripped out namespaces and using statements references. I am using Microsoft.Extensions.DependencyInjection where applicable though.

Startup.cs

public void ConfigureServices(IServiceCollection services) {

services.AddScoped<DataAccessLayerInterface> (options => new DataAccessLayerService("acorns"));

}

MyAPIController.cs

public class MyAPIController : Controller {
 public ActionResult tell_me() {
   MyService _business_service = new MyService();
   string _response = _business_service.tell_me_something();
   Hashtable _data = new Hashtable();
   _data.Add("squirrels eat", _response);
   return Json(_data);
 }
}

MyService.cs

public class MyService {
  DataAccessLayerInterface _DataAccessLayerService;
  public MyService(DataAccessLayerInterface MyDataAccessLayerService) {          
    this._DataAccessLayerService = MyDataAccessLayerService;
  }
  public string tell_me_something() {
    return _DataAccessLayerService.we_eat();
  }
}

DataAccessLayerHelper.cs

public interface DataAccessLayerInterface {
  string we_eat();
}
public class DataAccessLayerService : DataAccessLayerInterface {
  private string _food = "rocks";
  public string we_eat() {
    return this._food;
  }
  public DataAccessLayerService(string MyFood) {
    this._food = MyFood;
  }
}

If I add the constructor dependency injection in the controller and then just pass it down to each piece it technically works but architecturally seems like it is incorrect because the business services should not need any parameters and should be completely independent of the controller. i.e.:

private DataAccessLayerInterface _DataAccessLayerService;

public UserAPIController(DataAccessLayerInterface MyDataAccessLayerService) {
  this._DataAccessLayerService = MyDataAccessLayerService;
}

// in tell_me() action result, now I have to pass the object like so

string _response = _business_service.tell_me_something(_DataAccessLayerService);

Instead of constructor injection in the MyService.cs I also tried to just get the instance in the constructor like this and it didn't seem to work either...

public MyService() {
  this._DataAccessLayerService = (DataAccessLayerInterface)ServiceProvider.GetService(typeof(DataAccessLayerInterface));
}

It seems to me like the instance is created ok with dependency injection, but it only seems to work with controllers and not my own custom classes. Any suggestions?


Solution

  • There is 3 ways to solve your problem.

    1- Good way

    create an interface for your service like this

    public interface IMyService
    {
        string tell_me_something();
    }
    
    public class MyService : IMyService
    {
        private readonly DataAccessLayerInterface _DataAccessLayerService;
        public MyService(DataAccessLayerInterface MyDataAccessLayerService)
        {
            this._DataAccessLayerService = MyDataAccessLayerService;
        }
    
        public string tell_me_something()
        {
            return _DataAccessLayerService.we_eat();
        }
    }
    
    

    And just add it into DI

    services.AddScoped<IMyService, MyService>();
    

    And inject IMyService to controller.

    2- Inject MyService to DI

    services.AddScoped(provider=>
    new MyService(provider.GetRequiredService<DataAccessLayerInterface>());
    

    And inject MyService to controller;

    3- Bad way

    when create service instance use this:

    var service = new MyService(HttpContext.RequestServices.GetRequiredService<DataAccessLayerInterface>());