ChatGPT解决这个技术问题 Extra ChatGPT

无法从根提供程序.Net Core 2 解析范围服务

当我尝试运行我的应用程序时出现错误

InvalidOperationException: Cannot resolve 'API.Domain.Data.Repositories.IEmailRepository' from root provider because it requires scoped service 'API.Domain.Data.EmailRouterContext'.

奇怪的是,据我所知,这个 EmailRepository 和界面的设置与我的所有其他存储库完全相同,但没有为它们引发错误。仅当我尝试使用 app.UseEmailingExceptionHandling(); 时才会发生错误线。这是我的一些 Startup.cs 文件。

public class Startup
{
    public IConfiguration Configuration { get; protected set; }
    private APIEnvironment _environment { get; set; }

    public Startup(IConfiguration configuration, IHostingEnvironment env)
    {
        Configuration = configuration;

        _environment = APIEnvironment.Development;
        if (env.IsProduction()) _environment = APIEnvironment.Production;
        if (env.IsStaging()) _environment = APIEnvironment.Staging;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var dataConnect = new DataConnect(_environment);

        services.AddDbContext<GeneralInfoContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.GeneralInfo)));
        services.AddDbContext<EmailRouterContext>(opt => opt.UseSqlServer(dataConnect.GetConnectString(Database.EmailRouter)));

        services.AddWebEncoders();
        services.AddMvc();

        services.AddScoped<IGenInfoNoteRepository, GenInfoNoteRepository>();
        services.AddScoped<IEventLogRepository, EventLogRepository>();
        services.AddScoped<IStateRepository, StateRepository>();
        services.AddScoped<IEmailRepository, EmailRepository>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

        app.UseAuthentication();

        app.UseStatusCodePages();
        app.UseEmailingExceptionHandling();

        app.UseMvcWithDefaultRoute();
    }
}

这是电子邮件存储库

public interface IEmailRepository
{
    void SendEmail(Email email);
}

public class EmailRepository : IEmailRepository, IDisposable
{
    private bool disposed;
    private readonly EmailRouterContext edc;

    public EmailRepository(EmailRouterContext emailRouterContext)
    {
        edc = emailRouterContext;
    }

    public void SendEmail(Email email)
    {
        edc.EmailMessages.Add(new EmailMessages
        {
            DateAdded = DateTime.Now,
            FromAddress = email.FromAddress,
            MailFormat = email.Format,
            MessageBody = email.Body,
            SubjectLine = email.Subject,
            ToAddress = email.ToAddress
        });
        edc.SaveChanges();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
                edc.Dispose();
            disposed = true;
        }
    }
}

最后是异常处理中间件

public class ExceptionHandlingMiddleware
{
    private const string ErrorEmailAddress = "errors@ourdomain.com";
    private readonly IEmailRepository _emailRepository;

    private readonly RequestDelegate _next;

    public ExceptionHandlingMiddleware(RequestDelegate next, IEmailRepository emailRepository)
    {
        _next = next;
        _emailRepository = emailRepository;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex, _emailRepository);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception,
        IEmailRepository emailRepository)
    {
        var code = HttpStatusCode.InternalServerError; // 500 if unexpected

        var email = new Email
        {
            Body = exception.Message,
            FromAddress = ErrorEmailAddress,
            Subject = "API Error",
            ToAddress = ErrorEmailAddress
        };

        emailRepository.SendEmail(email);

        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int) code;
        return context.Response.WriteAsync("An error occured.");
    }
}

public static class AppErrorHandlingExtensions
{
    public static IApplicationBuilder UseEmailingExceptionHandling(this IApplicationBuilder app)
    {
        if (app == null)
            throw new ArgumentNullException(nameof(app));
        return app.UseMiddleware<ExceptionHandlingMiddleware>();
    }
}

更新:我发现这个链接 https://github.com/aspnet/DependencyInjection/issues/578 导致我从这里更改了我的 Program.cs 文件的 BuildWebHost 方法

public static IWebHost BuildWebHost(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build();
}

对此

public static IWebHost BuildWebHost(string[] args)
{
    return WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseDefaultServiceProvider(options =>
            options.ValidateScopes = false)
        .Build();
}

我不知道到底发生了什么,但它现在似乎有效。

那里发生的事情是没有验证范围嵌套;如在运行时,它不会检查范围级别的嵌套是否不正确。显然,这是在 1.1 中默认关闭的。一旦 2.0 出现,他们默认将其打开。
对于试图关闭 ValidateScopes 的任何人,请阅读此stackoverflow.com/a/50198738/1027250

T
TanvirArjel

您在 Startup 类中将 IEmailRepository 注册为范围服务。这意味着您不能将其作为构造函数参数注入 Middleware,因为只有 Singleton 服务可以通过 Middleware 中的构造函数注入来解析。您应该将依赖项移至 Invoke 方法,如下所示:

public ExceptionHandlingMiddleware(RequestDelegate next)
{
    _next = next;
}

public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{
    try
    {
        await _next.Invoke(context);
    }
    catch (Exception ex)
    {
        await HandleExceptionAsync(context, ex, emailRepository);
    }
}

哇!从来不知道你可以注入方法,这只是用于中间件还是我可以在我自己的方法中使用这个技巧?
注册为作用域的 IMiddleware 呢?我确定我得到了一个新的中间件实例,但我仍然无法向它注入范围服务。
@FergalMoran 不幸的是,这个“技巧”只是中间件的 Invoke 方法的一种特殊行为。但是,您可以通过 autofac IoC 库和属性注入实现类似的功能。请参阅ASP.NET Core MVC Dependency Injection via property or setter method?
注射不是魔术。幕后有一个引擎,它实际上调用依赖容器来生成实例,以作为参数传递给构造函数或方法。该特定引擎使用 HttpContext 的第一个参数查找名为“Invoke”的方法,然后为其余参数创建实例。
“只有 Singleton 服务可以通过中间件中的构造函数注入来解决”。刚学到新东西! (并解决了我的问题:-)
H
Hakan Fıstık

获取作用域依赖实例的另一种方法是将服务提供者 (IServiceProvider) 注入中间件构造函数,在 Invoke 方法中创建 scope,然后从作用域中获取所需的服务:

using (var scope = _serviceProvider.CreateScope()) {
    var _emailRepository = scope.ServiceProvider.GetRequiredService<IEmailRepository>();

    //do your stuff....
}

查看 asp.net core dependency injection best practices tips tricks 中的在方法主体中解析服务,了解更多详细信息。


超级有帮助,谢谢!对于任何试图在中间件中访问 EF 上下文的人来说,这是默认的方式,因为它们是作用域的。
起初我认为这不起作用,但后来我意识到您在第二行使用 scope.ServiceProvider 而不是 _serviceProvider。谢谢你。
如果您想使用 ServiceProvider.CreateScope(),请不要忘记 using Microsoft.Extensions.DependencyInjection;。见这里:docs.microsoft.com/en-us/dotnet/api/…
在 asp.net 过滤器中使用范围服务时,然后尝试在单例服务方法中获取相同的范围服务时,此处显示的方法提供的范围与过滤器中使用的范围不同。
J
Joe Audette

中间件始终是单例的,因此您不能在中间件的构造函数中将作用域依赖项作为构造函数依赖项。

中间件支持 Invoke 方法上的方法注入,因此您只需将 IEmailRepository emailRepository 作为参数添加到该方法中,它将被注入那里并且在作用域中会很好。

public async Task Invoke(HttpContext context, IEmailRepository emailRepository)
{

    ....
}

我遇到了类似的情况,然后我使用 AddTransient 添加了服务,它能够解决依赖关系。我认为它不起作用,因为中间件是单例的?有点怪..
我认为 Transient 依赖项必须手动处理,这与 scoped 不同,它将在首次创建它的 Web 请求结束时自动处理。也许作用域依赖内的瞬态一次性物品会在外部对象被释放时被释放。我仍然不确定单例中的瞬态依赖或具有比瞬态生命周期更长的对象是一个好主意,我想我会避免这种情况。
即使在这种情况下您可以通过构造函数注入瞬态范围的依赖项,它也不会像您想象的那样被实例化。它只会发生一次,当 Singleton 被构建时。
您提到中间件始终是单例的,但事实并非如此。可以将中间件创建为基于工厂的中间件并将其用作作用域中间件。
看起来基于工厂的中间件是在 asp.netcore 2.2 中引入的,而 documentation 是在 2019 年创建的。所以据我所知,当我发布它时,我的回答是正确的。今天,基于工厂的中间件看起来确实是一个很好的解决方案。
H
Harun Diluka Heshan

您的 middlewareservice 必须相互兼容才能通过 middlewareconstructor 注入 service。在这里,您的 middleware 已创建为 convention-based middleware,这意味着它充当 singleton service,并且您已将服务创建为 scoped-service。因此,您不能将 scoped-service 注入 singleton-service 的构造函数,因为它会强制 scoped-service 充当 singleton。但是,这是您的选择。

将您的服务作为参数注入 InvokeAsync 方法。如果可能的话,让你的服务成为一个单一的服务。将您的中间件转换为基于工厂的中间件。

Factory-based middleware 可以充当 scoped-service。因此,您可以通过该中间件的构造函数注入另一个 scoped-service。下面,我向您展示了如何创建 factory-based 中间件。

这仅用于演示。所以,我已经删除了所有其他代码。

public class Startup
{
    public Startup()
    {
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<TestMiddleware>();
        services.AddScoped<TestService>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMiddleware<TestMiddleware>();
    }
}

TestMiddleware

public class TestMiddleware : IMiddleware
{
    public TestMiddleware(TestService testService)
    {
    }

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        return next.Invoke(context);
    }
}

TestService

public class TestService
{
}