Search code examples
c#dependency-injectionparametersconstructorparameter-passing

c#: DI and additional parameter


For example, I have a class CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService with the following dependencies:

    protected readonly IDeviceService _deviceService;
    protected readonly IAzureFunctionLogService _azureFunctionLogService;
    protected readonly IDeviceValidationService _deviceValidationService;

so, I can create ctor for the class:

    public CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService(
        IDeviceService deviceService,
        IDeviceValidationService deviceValidationService,
        IAzureFunctionLogService azureFunctionLogService)
    {
        _deviceService = deviceService;
        _deviceValidationService = deviceValidationService;
        _azureFunctionLogService = azureFunctionLogService;
    }

then inject all dependencies like:

services.AddTransient<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>();
               services.AddSingleton<Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>>(sp =>
                   () => sp.GetRequiredService<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>()
               );

and then use it like this:

    private readonly Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService> _service;


        public FunctionDebugPendingStatusWorkflow(
Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService> service,
            //....
            ILoggerFactory loggerFactory)
        {
            _service = service;
            //....
            _logger = loggerFactory.CreateLogger<FunctionDebugPendingStatusWorkflow>();
        }

so, it works fine.

But how can I add one more param to ctor, which set in caller? For example, I want to pass deviceId to ctor and can't describe it as dependency using dependency injector in Program.cs (in my case)

I have to create "Init" method like this:

    public void Init(int deviceId)
    {
        _device = _deviceService.GetDeviceById(deviceId);
        // ...
    }

and add logic there.

Then I have to call _service.Init(...); before using _service methods. It works, but all disadvantages and potential problems are obviously (if forgot to call etc)

How to pass this parameter using DI ?


Solution

  • ✅ You can do it with .NET 6 Dependency Injection like this.

    First, create a .NET 6 Console App (with top-level statements) and add necessary NuGet packages.

    📦 Install NuGet packages:

    • Microsoft.Extensions.DependencyInjection.Abstractions, 6.0.0
    • Microsoft.Extensions.Hosting, 6.0.0

    1. Add usings:

      using Microsoft.Extensions.Hosting;
      using Microsoft.Extensions.DependencyInjection;
      
    2. Create a dependency that can be injected to our service and in other places:

      public class Dependency : IDependency
      {
          // Initialize Id with a random value to validate that it's the same instance for the same scope
          public Guid Id { get; init; } = Guid.NewGuid();
      }
      
      // Interface for DI
      public interface IDependency
      {
          public Guid Id { get; }
      }
      
    3. Define our service with custom ID per scope:

      public class RequestService : IRequestService
      {
          public IDependency Dependency { get; init; }
      
          private int _id;
      
          // Constructor is used for standard dependency injection
          public RequestService(IDependency dependency)
          {
              Dependency = dependency;
          }
      
          // Init method is used to set custom ID via DI.
          public void Init(int id)
          {
              _id = id;
          }
      }
      
      public interface IRequestService
      {
          IDependency Dependency { get; }
      
          void Init(int id);
      }
      
    4. Create a Host Builder:

      var builder = Host.CreateDefaultBuilder();
      // You can do exactly the same with an `WebApplicationBuilder`
      // WebApplication.CreateBuilder(args);
      
    5. Start configuring DI:

      builder.ConfigureServices(services =>
      {
          // Must be scoped
          services.AddScoped<IDependency, Dependency>();
          // Must be scoped
          services.AddScoped<IRequestService, RequestService>();
      
          // <Add a factory method DI configuration here>
      });
      

      5.1. Configure a factory to instantiate your service via DI and to set an arbitrary ID during that.

      💥 This is the main part of the DI magic

      Factory function will do:

      • Take int id as an input.
      • Instantiate IRequestService via DI (essentially, will create a RequestService object).
      • Calls .Init(id) with ID value passed as an argument.
      // Add into `builder.ConfigureServices(services => {})`
      
      // Must be scoped
      services.AddScoped<Func<int, IRequestService>>(implementationFactory: provider =>
      {
          // Create a new factory
          return new Func<int, IRequestService>(id =>
          {
              var result = provider.GetRequiredService<IRequestService>();
              result.Init(id);
              return result;
          });
      });
      
    6. After that just build the Host and create a DI scope:

      var host = builder.Build();
      
      var scope = host.Services.CreateScope();
      
    7. Finally, use the DI:

      // Resolve an `IDependency` just for the sake of the example
      var dependency = scope.ServiceProvider.GetRequiredService<IDependency>();
      
      // Resolve a factory for your service
      var requestServiceFactory = scope.ServiceProvider.GetRequiredService<Func<int, IRequestService>>();
      // Use a factory with custom ID
      var requestService = requestServiceFactory(32);
      

      Note: dependency and requestService.Dependency will be the same instance.


    So, if you inject Func<int, IRequestService> anywhere, you'll be able to instantiate your IRequestService using that factory with custom ID.

    👍 You will never forget to call .Init(id) as it's required by the function signature and compiler will not allow you to skip it.

    ❗ If your service is disposable, you would have to take care of it yourself. DI will not help you with it.

    I use a Generic Host and Console App but it will work exactly in the same way in the Web API or ASP.NET MVC application.


    Useful links: