루트 공급자 .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에서 생성자 삽입으로 서비스 MiddlewareSingleton해결할 수 있으므로 에서 생성자 매개 변수로 삽입 할 수 없습니다 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미들웨어 생성자) 생성 scopeInvoke있어서 다음 범위에서 필요한 서비스를받을 :

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. 당신 middlewarefactory-based하나로 바꾸십시오 .

A Factory-based middlewarescoped-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.