Kapsamlı hizmet kök sağlayıcıdan çözülemiyor .Net Core 2


92

Uygulamamı çalıştırmayı denediğimde hata alıyorum

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

Garip olan şey, bu EmailRepository'nin ve arayüzün, diğer depolarımın tümü gibi tam olarak anlayabildiğim kadarıyla aynı şekilde ayarlanmış olmasıdır, ancak onlar için herhangi bir hata atılmamıştır. Hata yalnızca uygulamayı kullanmayı denediğimde oluşur.KullanımEmailingExceptionHandling (); hat. İşte Startup.cs dosyamdan bazıları.

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

İşte 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;
        }
    }
}

Ve son olarak, istisna işleme ara yazılımları

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

Güncelleme: Bu bağlantıyı https://github.com/aspnet/DependencyInjection/issues/578 buldum, bu da Program.cs dosyamın BuildWebHost yöntemini bundan değiştirmeme neden oldu

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

buna

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

Tam olarak ne olduğunu bilmiyorum ama şimdi işe yarıyor gibi görünüyor.


4
Orada olan şey, kapsam iç içe yerleştirmenin doğrulanmamasıdır; içinde olduğu gibi, kapsam düzeyinde yanlış yuvalanmanız olup olmadığını çalışma zamanı sırasında kontrol etmez. Görünüşe göre, bu varsayılan olarak 1.1'de kapatıldı. 2.0 geldiğinde, varsayılan olarak açtılar.
Robert Burke

ValidateScopes'ı kapatmaya çalışan herkes için lütfen bu stackoverflow.com/a/50198738/1027250
Yorro

Yanıtlar:


188

Sınıfta IEmailRepositorykapsamlı bir hizmet olarak kaydettiniz Startup. Bu, onu bir yapıcı parametresi olarak enjekte edemeyeceğiniz anlamına gelir, Middlewareçünkü içinde yalnızca Singletonservisler yapıcı enjeksiyonu ile çözümlenebilir Middleware. Bağımlılığı şu şekilde Invokeyönteme taşımalısınız :

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

13
Vaov! Yöntemlere enjekte edebileceğinizi hiç bilmiyordum, bu sadece ara katman yazılımı için mi yoksa bu numarayı kendi yöntemlerimde kullanabilir miyim?
Fergal Moran

Kapsamlı olarak kaydedilen IMiddleware ne olacak? Yeni bir ara yazılım örneği aldığımdan eminim, ancak buna yine de kapsamlı bir hizmet enjekte edemiyorum.
Botis

2
@FergalMoran Maalesef, bu "numara" sadece ara katman yazılımının özel bir davranışıdır Invoke. Ancak, autofac IoC kitaplığı ve özellik enjeksiyonu aracılığıyla benzer bir şey elde edebilirsiniz. Özellik veya ayarlayıcı yöntemi aracılığıyla ASP.NET Core MVC Bağımlılık Enjeksiyonuna bakın ? .
B12Toaster

4
Enjeksiyon sihir değildir. Yapıcılara veya yöntemlere parametre olarak geçirilecek örnekler oluşturmak için gerçekte bağımlılık konteynerini çağıran perde arkasında bir motor vardır. Bu özel motor, HttpContext'in ilk bağımsız değişkeniyle "Çağır" adlı yöntemleri arar ve ardından geri kalan parametreler için örnekler oluşturur.
Thanasis İoannidis

97

Kapsamlı bağımlılık örneğini elde etmenin başka bir yolu, servis sağlayıcısını ( IServiceProvider) ara yazılım oluşturucusuna enjekte etmek scope, Invokeyöntemde oluşturmak ve ardından kapsamdan gerekli hizmeti almaktır:

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

    //do your stuff....
}

Check out bir yöntem Gövde Çözme Hizmetleri içinde asp.net çekirdek bağımlılık enjeksiyon iyi uygulamalar ipuçları hileler Daha fazla ayrıntı için.


5
Çok yardımcı oldu, teşekkürler! Ara yazılımda EF Contexts'e erişmeye çalışan herkes için bu, varsayılan olarak kapsama alanlarına göre gitmenin yoludur.
ntziolis


İlk başta bunun işe yaradığını düşünmemiştim, ama sonra ikinci satır scope.ServiceProvideryerine yaptığınızı fark ettim _serviceProvider. Bunun için teşekkürler.
adam0101

_serviceProvider.CreateScope (). ServiceProvider benim için daha iyisini yapıyor
XLR8

IServiceScopeFactoryBu amaçla kullanmanın en iyisi olacağını düşünüyorum
Francesco DM

27

Ara yazılım her zaman bir tekildir, bu nedenle ara yazılımınızın yapıcısında yapıcı bağımlılıkları olarak bağımlılıkları kapsamına alamazsınız.

Ara yazılım, Invoke yönteminde yöntem enjeksiyonunu destekler, bu nedenle IEmailRepository emailRepository'yi bu yönteme bir parametre olarak ekleyebilirsiniz ve oraya enjekte edilecek ve kapsamı dahilinde iyi olacaktır.

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

    ....
}

Ben de benzer bir durumdaydım, ardından AddTransient'i kullanarak hizmet ekledim ve bağımlılığı çözebildim. Ara katman singleton olduğu için işe yaramayacağını düşünmüştüm. biraz garip ..
Sateesh Pagolu

1
Bence bir Geçici bağımlılığın, ilk oluşturulduğu web isteğinin sonunda otomatik olarak atılacak olan kapsamın aksine, manuel olarak atılması gerekeceğini düşünüyorum. Belki kapsamlı bir bağımlılık içinde geçici bir atılabilir, dış nesne elden çıkarılır. Yine de bir singleton içinde geçici bir bağımlılığın veya geçici yaşam süresinden daha uzun bir nesnenin iyi bir fikir olduğundan emin değilim, bundan kaçınırım.
Joe Audette

2
Bu durumda yapıcı aracılığıyla Geçici kapsamlı bir bağımlılığı enjekte edebilseniz bile, düşündüğünüz gibi somutlaştırılmayacaktır. Singleton inşa edildiğinde yalnızca bir kez gerçekleşecek.
Jonathan

1
Ara yazılımın her zaman bir singleton olduğunu söylediniz, ancak bu doğru değil. Fabrika tabanlı bir ara yazılım olarak bir ara yazılım oluşturmak ve bunu kapsamlı bir ara yazılım olarak kullanmak mümkündür.
Harun Diluka Heshan

Görünüşe göre fabrika tabanlı ara yazılım asp.netcore 2.2'de tanıtıldı ve dokümantasyon 2019'da oluşturuldu. Yani bildiğim kadarıyla bunu yayınladığımda cevabım doğruydu. Fabrika tabanlı ara yazılım, bugün iyi bir çözüm gibi görünüyor.
Joe Audette

4

Sizin middlewareve serviceenjekte etmek amacıyla birbirleriyle uyumlu olmak zorundadır servicearacılığıyla constructor, aramalarınızdan middleware. Burada, sizin bir middlewareolarak yaratıldı, convention-based middlewarebu da onun bir gibi davrandığı anlamına gelir singleton serviceve hizmetinizi siz yarattınız scoped-service. Yani, a'nın scoped-serviceyapıcısına bir enjekte edemezsiniz singleton-serviceçünkü o, onu scoped-servicebir singletontek olarak hareket etmeye zorlar . Ancak, seçenekleriniz burada.

  1. Hizmetinizi InvokeAsyncyönteme bir parametre olarak ekleyin .
  2. Mümkünse hizmetinizi tekli yapın.
  3. Kendi dönüşüm middlewarea factory-basedone.

A Factory-based middleware, bir scoped-service. Böylece, scoped-servicebu ara yazılımın yapıcısı aracılığıyla başka bir tane enjekte edebilirsiniz . Aşağıda, bir factory-basedara yazılımın nasıl oluşturulacağını size gösterdim .

Bu sadece gösteri amaçlıdır. Bu yüzden, diğer tüm kodu kaldırdım.

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
{
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.