Không thể giải quyết dịch vụ có phạm vi từ nhà cung cấp gốc .Net Core 2


83

Khi tôi cố gắng chạy ứng dụng của mình, tôi gặp lỗi

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

Điều kỳ lạ là EmailRepository này và giao diện được thiết lập hoàn toàn giống như tôi có thể nói như tất cả các kho lưu trữ khác của tôi nhưng không có lỗi nào xảy ra cho chúng. Lỗi chỉ xảy ra nếu tôi cố sử dụng app.UseEmailingExceptionHandling (); hàng. Đây là một số tệp Startup.cs của tôi.

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

Đây là 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;
        }
    }
}

Và cuối cùng là phần mềm trung gian xử lý ngoại lệ

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

Cập nhật: Tôi tìm thấy liên kết này https://github.com/aspnet/DependencyInjection/issues/578 khiến tôi thay đổi phương thức BuildWebHost trong tệp Program.cs của mình từ đó

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

đến cái này

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

Tôi không biết chính xác điều gì đang xảy ra nhưng nó có vẻ hoạt động ngay bây giờ.


4
Điều gì đang xảy ra ở đó, đó là việc lồng trong phạm vi không được xác thực; như trong, nó không kiểm tra, trong thời gian chạy, nếu bạn có lồng cấp phạm vi không đúng. Rõ ràng, điều này đã bị tắt theo mặc định trong 1.1. Sau khi 2.0 xuất hiện, họ đã bật nó theo mặc định.
Robert Burke

Đối với bất kỳ ai đang cố gắng tắt ValidateScope, vui lòng đọc stackoverflow.com/a/50198738/1027250
Yorro

Câu trả lời:


174

Bạn đã đăng ký IEmailRepositorynhư một dịch vụ có phạm vi, trong Startuplớp. Điều này có nghĩa là bạn không thể đưa nó vào dưới dạng một tham số khởi tạo Middlewarevì chỉ Singletoncác dịch vụ mới có thể được giải quyết bằng cách đưa vào hàm tạo Middleware. Bạn nên chuyển phần phụ thuộc sang Invokephương thức như sau:

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
Chà! Bạn chưa bao giờ biết rằng bạn có thể đưa vào các phương thức, đây chỉ dành cho phần mềm trung gian hay tôi có thể sử dụng thủ thuật này trong các phương pháp của riêng mình?
Fergal Moran,

Điều gì về IMiddleware được đăng ký là phạm vi? Tôi biết chắc chắn rằng tôi nhận được một phiên bản mới của phần mềm trung gian, nhưng tôi vẫn không thể đưa dịch vụ có phạm vi vào nó.
Botis

2
@FergalMoran Thật không may, "thủ thuật" này là một hành vi đặc biệt của Invokephương thức phần mềm trung gian . Tuy nhiên, bạn có thể đạt được điều gì đó tương tự thông qua IoC lib tự động lấy nét và chèn thuộc tính. Xem ASP.NET Core MVC Dependency Injection thông qua thuộc tính hoặc phương thức setter? .
B12Toaster

4
Tiêm không phải là ma thuật. Có một công cụ đằng sau hậu trường thực sự gọi vùng chứa phụ thuộc để tạo ra các thể hiện để chuyển dưới dạng tham số cho các hàm tạo hoặc phương thức. Công cụ cụ thể này tìm kiếm các phương thức có tên "Gọi" với một đối số đầu tiên là HttpContext và sau đó tạo các thể hiện cho phần còn lại của các tham số.
Thanasis Ioannidis

86

Một cách khác để có được phiên bản của phụ thuộc theo phạm vi là đưa nhà cung cấp dịch vụ ( IServiceProvider) vào phương thức khởi tạo phần mềm trung gian, tạo scopetrong Invokephương thức và sau đó nhận dịch vụ được yêu cầu từ phạm vi:

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

    //do your stuff....
}

Kiểm tra các dịch vụ giải quyết trong một cơ thể phương pháp trong asp.net các mẹo thực hành tốt nhất về phương pháp tiêm phụ thuộc cốt lõi để biết thêm chi tiết.


5
Siêu hữu ích, cảm ơn! Đối với bất kỳ ai đang cố gắng truy cập EF Contexts trong phần mềm trung gian, đây là cách để thực hiện vì chúng được xác định phạm vi theo mặc định.
ntziolis

stackoverflow.com/a/49886317/502537 thực hiện điều này trực tiếp hơn
RickAndMSFT

Lúc đầu, tôi không nghĩ điều này hiệu quả, nhưng sau đó tôi nhận ra bạn đang làm scope.ServiceProviderthay vì _serviceProviderở dòng thứ hai. Cám ơn vì cái này.
adam0101

_serviceProvider.CreateScope (). ServiceProvider làm tốt hơn cho tôi
XLR8

Tôi nghĩ tốt nhất nên sử dụng IServiceScopeFactorycho mục đích này
Francesco DM

27

Phần mềm trung gian luôn là một singleton vì vậy bạn không thể có các phụ thuộc phạm vi như các phụ thuộc phương thức khởi tạo trong phương thức khởi tạo của phần mềm trung gian của bạn.

Phần mềm trung gian hỗ trợ chèn phương thức trên phương thức Invoke, vì vậy bạn có thể chỉ cần thêm IEmailRepository emailRepository làm tham số cho phương thức đó và nó sẽ được đưa vào đó và sẽ ổn khi có phạm vi.

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

    ....
}

Tôi cũng ở trong tình huống tương tự, sau đó tôi đã thêm dịch vụ bằng AddTransient và nó có thể giải quyết sự phụ thuộc. Tôi nghĩ rằng nó sẽ không hoạt động vì phần mềm trung gian là singleton? hơi lạ ..
Sateesh Pagolu

1
Tôi nghĩ rằng phụ thuộc tạm thời sẽ phải được xử lý theo cách thủ công, không giống như phạm vi sẽ được tự động xử lý ở cuối yêu cầu web nơi nó được tạo lần đầu tiên. Có thể một đối tượng dùng một lần nhất thời bên trong phụ thuộc có phạm vi sẽ được xử lý đối tượng bên ngoài được xử lý. Tôi vẫn không chắc sự phụ thuộc thoáng qua bên trong một singleton hoặc một đối tượng có thời gian tồn tại lâu hơn nhất thời là một ý tưởng hay, tôi nghĩ rằng tôi sẽ tránh điều đó.
Joe Audette

2
Mặc dù bạn có thể đưa phụ thuộc vào phạm vi thoáng qua thông qua hàm tạo trong trường hợp này, nó sẽ không được khởi tạo như bạn nghĩ. Nó sẽ chỉ xảy ra một lần, khi Singleton được chế tạo.
Jonathan

1
Bạn đã đề cập rằng phần mềm trung gian luôn là một singleton, nhưng điều đó không đúng. Có thể tạo một phần mềm trung gian dưới dạng phần mềm trung gian dựa trên cơ sở nhà máy và sử dụng nó như một phần mềm trung gian theo phạm vi.
Harun Diluka Heshan

Có vẻ như phần mềm trung gian dựa trên nhà máy đã được giới thiệu trong asp.netcore 2.2 và tài liệu đã được tạo vào năm 2019. Vì vậy, câu trả lời của tôi là đúng khi tôi đăng nó theo những gì tôi biết. Phần mềm trung gian dựa trên nhà máy trông giống như một giải pháp tốt hiện nay.
Joe Audette

4

Của bạn middlewarevà của servicephải tương thích với nhau để tiêm servicethông qua constructorcủa bạn middleware. Ở đây, của bạn middlewaređã được tạo dưới dạng một convention-based middlewarecó nghĩa là nó hoạt động như một singleton servicevà bạn đã tạo dịch vụ của mình với tư cách là scoped-service. Vì vậy, bạn không thể đưa a scoped-servicevào hàm tạo của a singleton-servicevì nó buộc hàm scoped-servicephải hoạt động như một singleton. Tuy nhiên, đây là lựa chọn của bạn.

  1. Chèn dịch vụ của bạn làm tham số cho InvokeAsyncphương thức.
  2. Hãy biến dịch vụ của bạn thành một dịch vụ duy nhất, nếu có thể.
  3. Chuyển đổi của bạn middlewarethành một factory-based.

A Factory-based middlewarecó thể hoạt động như một scoped-service. Vì vậy, bạn có thể chèn một cái khác scoped-servicethông qua phương thức khởi tạo của phần mềm trung gian đó. Dưới đây, tôi đã hướng dẫn bạn cách tạo factory-basedphần mềm trung gian.

Điều này chỉ để trình diễn. Vì vậy, tôi đã loại bỏ tất cả các mã khác.

public class Startup
{
    public Startup()
    {
    }

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

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

Các TestMiddleware:

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

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

Các TestService:

public class TestService
{
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.