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

geo*_*rtz 53 c# asp.net-core asp.net-core-2.0

当我尝试运行我的应用程序时,我收到错误

InvalidOperationException: Cannot resolve 'API.Domain.Data.Repositories.IEmailRepository' from root provider because it requires scoped service 'API.Domain.Data.EmailRouterContext'.
Run Code Online (Sandbox Code Playgroud)

奇怪的是,这个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后是异常处理中间件

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

对此

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

我不知道究竟发生了什么,但它现在似乎有效.

use*_*336 103

IEmailRepositoryStartup课程中注册了作为范围的服务.这意味着您不能将其作为构造函数参数注入,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 哇!从来不知道你可以注入方法,这只是针对中间件还是我可以在我自己的方法中使用这个技巧? (7认同)
  • “只有单例服务可以通过中间件中的构造函数注入来解决”。刚刚学到了新东西!(并解决了我的问题:-) (6认同)
  • 注射并不是魔法。幕后有一个引擎,它实际上调用依赖项容器来生成实例,并将其作为参数传递给构造函数或方法。该特定引擎查找第一个参数为 HttpContext 的名为“Invoke”的方法,然后为其余参数创建实例。 (5认同)
  • @FergalMoran不幸的是,这个“技巧”只是中间件的Invoke方法的一种特殊行为。但是,您可以通过autofac IoC库和属性注入实现类似的功能。请参阅[通过属性或设置器方法进行ASP.NET Core MVC依赖注入?](/sf/ask/3437857111/ )。 (2认同)

Rid*_*dik 38

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

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

    //do your stuff....
}
Run Code Online (Sandbox Code Playgroud)

退房解决服务中的方法体asp.net核心依赖注入的最佳实践技巧窍门的更多细节.

  • 超级有帮助,谢谢!对于任何尝试在中间件中访问 EF 上下文的人来说,这是一种可行的方法,因为它们默认是有作用域的。 (6认同)
  • 起初我认为这不起作用,但后来我意识到你在第二行执行的是“scope.ServiceProvider”而不是“_serviceProvider”。谢谢你。 (2认同)
  • 如果您想使用“ServiceProvider.CreateScope()”,请不要忘记“using Microsoft.Extensions.DependencyInjection;”。请参阅此处:https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions.createscope?view=dotnet-plat-ext-5.0 (2认同)

Joe*_*tte 21

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

中间件支持对Invoke方法进行方法注入,因此您只需将IEmailRepository emailRepository作为参数添加到该方法中,它就会被注入,并且可以作为作用域使用.

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

    ....
}
Run Code Online (Sandbox Code Playgroud)

  • 您已经提到中间件始终是单例,但事实并非如此。可以将中间件创建为基于工厂的中间件并将其用作作用域中间件。 (3认同)
  • 即使在这种情况下,您可以通过构造函数注入瞬态作用域的依赖项,也不会像您想象的那样实例化该依赖项。在构建Singleton时,它只会发生一次。 (2认同)

Har*_*han 13

您的middlewareservice必须彼此兼容才能service通过 constructor 您的注入middleware。在这里,您middleware已创建为 a,convention-based middleware这意味着它充当 a singleton service,并且您已将服务创建为scoped-service。因此,您不能将 a 注入scoped-service到 a 的构造函数中singleton-service,因为它会强制 ascoped-service充当 1 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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

TestMiddleware

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

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        return next.Invoke(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

TestService

public class TestService
{
}
Run Code Online (Sandbox Code Playgroud)


小智 11

在 .NET Core 6 中,以下设置对我有用。

using (var scope = app.Services.CreateScope())
 {
     var services = scope.ServiceProvider.GetRequiredService<IDbInitilizer>;
     services.Invoke().Initialize();
 }
Run Code Online (Sandbox Code Playgroud)

数据库初始化器