【问题标题】:Unable to Consume Scoped Service via MediatR无法通过 MediatR 使用范围服务
【发布时间】:2021-01-11 04:37:13
【问题描述】:

我有一个运行 .NET 5 和 C# 9 的 ASP.NET Core 应用程序。它还在后台运行 Discord 机器人。我在Startup.cs 中的ConfigureServices() 方法看起来像这样。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    
    var client = new DiscordSocketClient(new DiscordSocketConfig
    {
        AlwaysDownloadUsers = true,
        MessageCacheSize = 10000,
            
        GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMessages |
                         GatewayIntents.GuildMessageReactions | GatewayIntents.GuildPresences,
            
        LogLevel = LogSeverity.Info
    });
        
    var commandService = new CommandService(new CommandServiceConfig
    {
        LogLevel = LogSeverity.Debug,
        DefaultRunMode = RunMode.Sync,
        CaseSensitiveCommands = false,
        IgnoreExtraArgs = false,
    });
    
    services
        .AddMediatR(Assembly.GetEntryAssembly())
        .AddHostedService<StartupService>()
        .AddHostedService<DiscordListener>()
        .AddScoped<ITestService, TestService>()
        .AddSingleton(client)
        .AddSingleton(provider =>
        {
            commandService.AddModulesAsync(Assembly.GetEntryAssembly(), provider);
            return commandService;
        })
        .AddSingleton(Configuration);
}

如您所见,我已将 ITestService 和 TestService 添加为范围服务。

public class TestService : ITestService
{
    public async Task<string> GetString()
    {
        await Task.Delay(1);
        return "hey";
    }
}

public interface ITestService
{
    Task<string> GetString();
}

然后我将这个服务注入到我的命令模块中。

public class TestModule : ModuleBase<SocketCommandContext>
{
    private readonly ITestService _testService;

    public TestModule(ITestService testService)
    {
        _testService = testService;
    }

    [Command("ping")]
    public async Task Ping()
    {
        var str = await _testService.GetString();
        await ReplyAsync(str);
    }
}

但是,应用程序不响应ping 命令。事实上,我的接收消息的处理程序根本没有被命中(我已经通过断点检查过)。这是监听事件并发布相关 MediatR 通知的托管服务。

public partial class DiscordListener : IHostedService
{
    private readonly DiscordSocketClient _client;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public DiscordListener(
        DiscordSocketClient client,
        IServiceScopeFactory serviceScopeFactory)
    {
        _client = client;
        _serviceScopeFactory = serviceScopeFactory;
    }
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _client.MessageReceived += MessageReceived;
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _client.MessageReceived -= MessageReceived;
        return Task.CompletedTask;
    }

    // Creating our own scope here
    private async Task MessageReceived(SocketMessage message)
    {
        using var scope = _serviceScopeFactory.CreateScope();
        var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
        await mediator.Publish(new MessageReceivedNotification(message));
    }
}

这是处理通知的通知处理程序。

public class CommandListener : INotificationHandler<MessageReceivedNotification>
{
    private readonly IConfiguration _configuration;
    private readonly DiscordSocketClient _client;
    private readonly CommandService _commandService;
    private readonly IServiceProvider _serviceProvider;

    public CommandListener(
        IConfiguration configuration,
        DiscordSocketClient client, 
        CommandService commandService, 
        IServiceProvider serviceProvider)
    {
        _configuration = configuration;
        _client = client;
        _commandService = commandService;
        _serviceProvider = serviceProvider;
    }
    
    public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
    {
        if (!(notification.Message is SocketUserMessage message)
            || !(message.Author is IGuildUser user)
            || user.IsBot)
        {
            return;
        }
        
        var argPos = 0;
        var prefix = _configuration["Prefix"];
        
        if (message.HasStringPrefix(prefix, ref argPos))
        {
            var context = new SocketCommandContext(_client, message);
            using var scope = _serviceProvider.CreateScope();
            await _commandService.ExecuteAsync(context, argPos, scope.ServiceProvider);
        }
    }
}

澄清一下,_client.MessageReceoved += ... 的断点没有被命中。如果我将ITestService 和TestService 实现更改为Singleton,则处理程序被命中并且命令按预期工作。知道我做错了什么吗?

Here 是项目的 GitHub 存储库,如果您想查看完整代码。它不是太大。

【问题讨论】:

    标签: c# asp.net-mvc asp.net-core discord.net mediatr


    【解决方案1】:

    这是混合单例和范围服务时的典型问题。如果您最终遇到单例正在解析范围服务的情况,则不允许这样做。

    来自此处的文档 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1

    不要从单例解析范围服务。可能会导致服务在处理后续请求时状态不正确。没关系:

    从作用域或瞬态服务中解析单例服务。 从另一个范围服务或临时服务中解析一个范围服务。 默认情况下,在开发环境中,从另一个生命周期较长的服务中解析一个服务会引发异常。有关详细信息,请参阅范围验证。

    还有更多关于https://dotnetcoretutorials.com/2018/03/20/cannot-consume-scoped-service-from-singleton-a-lesson-in-asp-net-core-di-scopes/amp/的讨论

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      • 2018-09-27
      • 1970-01-01
      • 1970-01-01
      • 2019-01-08
      相关资源
      最近更新 更多