无法解析来自根提供商.Net Core 2的范围服务


83

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

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

奇怪的是,据我所知,这个EmailRepository和接口的设置与我所有其他存储库完全相同,但没有引发任何错误。仅当我尝试使用该应用程序时,才会发生该错误。线。这是我的一些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();
    }
}

这是EmailRepository

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

我不知道到底发生了什么,但现在似乎可以了。


4
那里发生的是,作用域嵌套未得到验证;就像这样,它不会在运行时检查范围级别是否嵌套不正确。显然,默认情况下在1.1中已将其关闭。一旦2.0出现,他们默认就将其打开。
罗伯特·伯克

对于任何试图关闭ValidateScopes的人,请阅读以下stackoverflow.com/a/50198738/1027250
Yorro

Answers:


174

您已在类IEmailRepository中将其注册为范围服务Startup。这意味着您不能将其作为构造函数参数注入,因为构造函数注入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);
    }
}

12
哇!从来不知道您可以注入方法,这只是用于中间件,还是我可以在自己的方法中使用此技巧?
Fergal Moran

注册为作用域的IMiddleware怎么样?我肯定知道我得到了一个中间件的新实例,但是我仍然无法为其注入范围服务。
Botis

2
@FergalMoran不幸的是,这个“技巧”只是中间件Invoke方法的一种特殊行为。但是,您可以通过autofac IoC库和属性注入实现类似的功能。通过属性或设置器方法查看ASP.NET Core MVC依赖注入吗?
B12Toaster

4
注射不是魔术。幕后有一个引擎实际上在调用依赖项容器以生成实例以作为参数传递给构造函数或方法。这个特殊的引擎使用第一个参数HttpContext查找名为“ Invoke”的方法,然后为其余参数创建实例。
Thanasis Ioannidis

86

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

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

    //do your stuff....
}

asp.net核心依赖项注入最佳实践提示方法中查看解决方案服务中的技巧,以获取更多详细信息。


5
超级有帮助,谢谢!对于尝试在中间件中访问EF上下文的任何人,这都是默认情况下采用的方法。
ntziolis


起初我认为这没有用,但是后来我意识到您正在这样做,scope.ServiceProvider而不是_serviceProvider在第二行。谢谢你
adam0101 '19

_serviceProvider.CreateScope()。ServiceProvider对我而言更好
XLR8

我认为最好将其IServiceScopeFactory用于此目的
Francesco DM

27

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

中间件在Invoke方法上支持方法注入,因此您只需将IEmailRepository emailRepository作为该方法的参数添加,它将被注入该方法并在范围内可以正常使用。

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

    ....
}

我处于类似的情况,然后我使用AddTransient添加了服务,它能够解决依赖关系。我以为中间件是单身人士就行不通?有点奇怪..
Sateesh Pagolu

1
我认为Transient依赖关系将必须手动处理,与scoped不同,scoped将在首次创建Web请求的结尾自动处理。也许在范围依赖内的瞬态可弃对象将被处置,而外部对象被处置。仍然我不确定单例内部的瞬态依赖关系或具有比瞬态生存期更长的对象是否是个好主意,我想我会避免这种情况。
Joe Audette

2
即使在这种情况下,您可以通过构造函数注入瞬态作用域的依赖项,也不会像您想象的那样实例化该依赖项。在构建Singleton时,它只会发生一次。
乔纳森

1
您已经提到中间件始终是单例,但事实并非如此。可以将中间件创建为基于工厂的中间件,并将其用作范围限定的中间件。
哈伦·迪卢卡(Heun Diluka)鹤山

看起来像基于工厂的中间件是在asp.netcore 2.2中引入的,文档是在2019年创建的。所以,据我所知,我的回答是正确的。今天,基于工厂的中间件确实是一个不错的解决方案。
乔·奥黛特

4

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

  1. 将服务作为InvokeAsync方法的参数注入。
  2. 如果可能,使您的服务成为单身人士。
  3. 将您middleware变成factory-based一个。

AFactory-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
{
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.