【问题标题】:Dependency Injection / duplication of service added as Singleton依赖注入/重复服务添加为单例
【发布时间】:2020-04-24 23:25:27
【问题描述】:

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

我尝试将控制器添加为单例并使用控制器的 StartFeed() 方法来实例化该类,但无论我对 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();
        }
    }
}

【问题讨论】:

  • 我在这里看不到任何明显的东西。 调用AddSingleton之后,你有错误的AddScopedAddTransientISubService吗?
  • @Mayo 我添加了 start feed,它创建了 sftpService 的本地副本,在调用 stopFeed() 时将其处理掉
  • @KirkLarkin 我不这么认为,但我会再次完成启动并密切关注 Scoped 或 Transient 分配
  • 如果您想要长期运行的服务,您应该将其注册为 background 托管服务。检查Background tasks with hosted services in ASP.NET Core。您如何确定构造函数被调用了两次?构造函数中没有记录
  • 你怎么知道它是一个不同的实例?尝试使用 GUID 设置私有字段并在调试时检查该字段以查看它是否实际上是不同的实例。

标签: c# asp.net-core dependency-injection asp.net-core-2.2


【解决方案1】:

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

我已将逻辑移到 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();
}

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

    • 谢谢你,我从来不知道有像 IOptionsMonitor / IOptionsSnapshot 这样的东西我已经稍微改变了我的代码来满足这个...无论如何找到我的问题 - 很快就会自我回答.
    猜你喜欢
    • 2019-01-12
    • 1970-01-01
    • 1970-01-01
    • 2021-07-03
    • 2017-08-22
    • 1970-01-01
    • 2020-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多