Search code examples
c#asp.net-coredependency-injectionasp.net-core-2.2

Dependency Injection / duplication of service added as Singleton


I can't figure out why my dependency injection is not working as expected, when my controller is hit the constructor of MyFirstService is being hit again and therefore I am hitting a different cancellation token to the one I wish to be when calling the StopFeeds() method.

I have tried to add the controller in as a singleton and use the controller's StartFeed() method to instantiate the class but no matter what I do with DI (general ctor DI, explicit property assignment, [FromServices] and even directly passing in the service collection) when I hit stop feeds it will create another instance of MyFirstService ... Any ideas?


Interface:

public interface IFirstService : IService
{
    OrderDto CreateOrder(Order order);
    Task<string> ProcessOrder(string orderXml);
    void ProcessLineItems(ref List<NewLineItem> items, ref int lineNum, Item i, string orderId);
    NewOrderEvent NewOrderEvent(OrderDto newOrder, Order order, List<NewLineItem> lineItems);
}

MyFirstService :

public class MyFirstService : IFirstService
{
    private SftpService _sftpService;
    private readonly ITimer<MyService> _timer;
    private readonly IMediator _mediator;
    private readonly ILogger<MyService> _logger;
    private readonly MyFirstConfig _iOptions;

    private CancellationTokenSource CancellationTokenSource;

    public MyService(IMediator mediator, ILogger<MyService> logger, IOptionsSnapshot<MyFirstConfig> iOptions)
    {
        _mediator = mediator;
        _timer = new Timer<MyFirstService>(logger);
        _logger = logger;
        _iOptions = iOptions.Value;

        CancellationTokenSource = new CancellationTokenSource();
        CancellationTokenSource.Token.Register(FeedStopped);
    }

    private void CreateSftpService()
    {
        _sftpService = new SftpService(_iOptions.SftpOptions);
    }

    public void StartFeed()
    {
        CreateSftpService();
        StartFeed(TimerSchedule);
    }

    public void StartFeed(TimeSpan timeSpan)
    {
        _timer.ScheduleCallback(timeSpan, ProcessOrderFeedAsync, CancellationTokenSource.Token);
    }

    public void StopFeed()
    {
        CancellationTokenSource.Cancel();
        _sftpService.Dispose();
    }

Startup:

services.Configure<MyFirstConfig>(Configuration.GetSection("FirstSection"));
services.Configure<MySecondConfig>(Configuration.GetSection("SecondSection"));

services.AddSingleton<IFirstService, MyFirstService>();
services.AddSingleton<ISecondService, MySecondService>();

var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetRequiredService<IFirstService>().StartFeed();
serviceProvider.GetRequiredService<ISecondService>().StartFeed();

Controller: (I do handle other status codes, I stripped out the try/catch as they are irrelevant)

using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;
using SFTP.Services;
using System;

namespace API.Controllers
{
    [Route("api/[controller]/")]
    [ApiController]
    public class MyController : Controller
    {
        [HttpPost]
        [Route("feeds/start")]
        public IActionResult StartFeed([FromServices] IFirstService myService)
        {
            myService.StartFeed();
            return Ok();
        }

        [HttpPost]
        [Route("feeds/stop")]
        public IActionResult StopFeed([FromServices] IFirstService myService)
        {
            myService.StopFeed();
            return Ok();
        }
    }
}

Solution

  • The issue was that a service provider was being generated and used to start the feeds as part of configure services so that when the dependency injection was resolving itself a new instantiation was being forced.

    I have moved the logic in to the Configure() method and IApplicationBuilder now handles starting the feeds to generate the instance of each service:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<MyFirstConfig>(Configuration.GetSection("Next"));
        services.Configure<MySecondConfig>(Configuration.GetSection("CustomGateway"));
    
        services.AddSingleton<IFirstService, MyFirstService>();
        services.AddSingleton<ISecondService, MySecondService>();
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.ApplicationServices.GetRequiredService<IFirstService>().StartFeed();
    }