루트 공급자 .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와 인터페이스가 다른 모든 저장소와 동일하게 설정되어 있지만 오류가 발생하지 않는다는 것입니다. 이 오류는 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();
    }
}

다음은 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이 나오면 기본적으로 켜졌습니다.
— Robert Burke

ValidateScopes를 끄려는 사람은이 stackoverflow.com/a/50198738/1027250
— Yorro

답변:


174

클래스 IEmailRepository에서 범위 서비스로을 등록했습니다 Startup. 즉, .NET에서 생성자 삽입으로 서비스 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 2010 년

2
@FergalMoran 불행히도이 "속임수"는 미들웨어 Invoke방법 의 특별한 동작입니다 . 그러나 autofac IoC lib 및 속성 삽입을 통해 비슷한 것을 얻을 수 있습니다. 속성 또는 setter 메서드를 통한 ASP.NET Core MVC 종속성 주입을 참조하세요 ? .
— B12Toaster

4
주입은 마술이 아닙니다. 실제로 종속성 컨테이너를 호출하여 생성자 또는 메서드에 매개 변수로 전달할 인스턴스를 생성하는 엔진이 있습니다. 이 특정 엔진은 HttpContext의 첫 번째 인수가있는 "Invoke"라는 메서드를 찾은 다음 나머지 매개 변수에 대한 인스턴스를 만듭니다.
— Thanasis Ioannidis

86

범위의 의존성의 인스턴스를 얻는 또 다른 방법은 서비스 제공자 (주입하는 IServiceProvider미들웨어 생성자) 생성 scope에 Invoke있어서 다음 범위에서 필요한 서비스를받을 :

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

    //do your stuff....
}

자세한 내용 은 asp.net 핵심 종속성 주입 모범 사례 팁 트릭 에서 메서드 본문의 서비스 해결을 확인 하십시오.


5
매우 유용합니다, 감사합니다! 미들웨어에서 EF 컨텍스트에 액세스하려는 모든 사람에게 이것이 기본적으로 범위가 지정되는 방식입니다.
— ntziolis

stackoverflow.com/a/49886317/502537는 보다 직접적으로이 작업을 수행
— RickAndMSFT

처음에는 이것이 효과가 있다고 생각하지 않았지만 , 두 번째 라인 scope.ServiceProvider대신에 당신이하고 있다는 것을 깨달았습니다 _serviceProvider. 감사합니다.
— adam0101 19 dec

_serviceProvider.CreateScope (). ServiceProvider가 나에게 더 좋습니다
— XLR8

IServiceScopeFactory이 용도 로 사용 하는 것이 가장 좋을 것 같습니다
— Francesco DM

27

미들웨어는 항상 싱글 톤이므로 미들웨어 생성자에서 생성자 종속성으로 범위 종속성을 가질 수 없습니다.

미들웨어는 Invoke 메서드에 대한 메서드 삽입을 지원하므로 IEmailRepository emailRepository를 해당 메서드에 매개 변수로 추가하기 만하면 해당 메서드에 삽입되고 범위가 지정됩니다.

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

    ....
}

비슷한 상황에서 AddTransient를 사용하여 서비스를 추가했고 종속성을 해결할 수있었습니다. 미들웨어가 싱글 톤이기 때문에 작동하지 않을 것이라고 생각 했습니까? 약간 이상 ..
— Sateesh Pagolu

1
처음 생성되는 웹 요청의 끝에서 자동으로 처리되는 범위와 달리 일시적 종속성은 수동으로 처리해야한다고 생각합니다. 범위가 지정된 종속성 내부의 일시적인 일회용이 외부 개체가 삭제 될 수 있습니다. 여전히 싱글 톤 내부의 일시적인 종속성이나 일시적인 수명보다 긴 개체가 좋은 생각인지 모르겠습니다. 나는 그것을 피할 것이라고 생각합니다.
— Joe Audette

2
이 경우 생성자를 통해 Transient 범위 종속성을 주입 할 수 있지만 생각하는대로 인스턴스화되지 않습니다. Singleton이 구축 될 때 한 번만 발생합니다.
— Jonathan

1
미들웨어는 항상 싱글 톤이라고 언급했지만 사실이 아닙니다. 미들웨어를 공장 기반 미들웨어로 생성하여 범위 미들웨어로 사용할 수 있습니다.
— Harun Diluka Heshan

공장 기반 미들웨어가 asp.netcore 2.2에 도입되었고 문서 가 2019 년에 생성 된 것 같습니다. 그래서 제가 아는 한 게시했을 때 제 대답은 사실이었습니다. 공장 기반 미들웨어는 오늘날 좋은 솔루션처럼 보입니다.
— Joe Audette

4

당신 middleware과는 service를 주입하기 위해 서로 호환되어야합니다 service를 통해 constructor당신의 middleware. 여기에서 귀하 middleware는으로 생성되었으며 convention-based middleware이는으로 작동하고 singleton service서비스를 scoped-service. 그래서, 당신은을 주입 할 수 scoped-servicea의 생성자 singleton-service는이 강제 때문에 scoped-serviceA와 역할을 singleton하나. 그러나 여기에 옵션이 있습니다.

  1. 서비스를 매개 변수로 InvokeAsync메소드에 삽입하십시오 .
  2. 가능하면 서비스를 싱글 톤으로 만드십시오.
  3. 당신 middleware을 factory-based하나로 바꾸십시오 .

A 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
{
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.