依赖注入/服务重复添加为单例

Dependency Injection / duplication of service added as Singleton

我不明白为什么我的依赖注入没有按预期工作,当我的控制器被击中时,MyFirstService 的构造函数再次被击中,因此我击中了一个不同的取消标记我希望在调用 StopFeeds() 方法时成为。

我尝试将控制器添加为单例并使用控制器的 StartFeed() 方法实例化 class 但无论我用 DI 做什么(通用 ctor DI,显式 属性 分配,[FromServices] 甚至直接传递服务集合)当我点击停止提要时,它将创建另一个 MyFirstService 实例......有什么想法吗?


接口:

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);
}

我的第一个服务:

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();
    }

启动:

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();

控制器:(我处理其他状态代码,我去掉了 try/catch 因为它们不相关)

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();
        }
    }
}

这不是一个答案(因此是社区 wiki)。然而,我觉得这很重要,而且不可能通过评论清楚地涵盖。

您不应该将 IConfiguration 注入到这样的服务中。这在您的配置和服务之间创建了紧密耦合,包括获取配置的方法 (Microsoft.Extensions.Configuration) 和像 MySection 这样的魔术字符串。如果配置的格式应该改变,你的服务就会中断,因为它取决于它实际上不应该知道的事情(配置中有一个 MySection 部分的知识,以及如何检索配置)关于什么。

相反,您应该始终只注入您实际需要的信息,即 HostUsernamePassword 等的值。当有很多这样的标记时,您可能会考虑创建一个选项 class 来封装它们,然后注入它。例如:

public class SftpServiceOptions
{
    public string Host { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    ...
}

然后,您甚至可以使用选项模式从您的配置中轻松绑定值:

services.Configure<SftpServiceOptions>(Configuration.GetSection("MySection"));

现在,魔法字符串在您的应用程序设置中而不是一些随机服务中,如果它发生变化,它会变得更加明显和更容易追踪。

问题是正在生成一个服务提供者并将其作为配置服务的一部分用于启动提要,这样当依赖项注入解决自身问题时,就会强制执行一个新的实例化。

我已将逻辑移至 Configure() 方法,IApplicationBuilder 现在处理启动提要以生成每个服务的实例:

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();
}