Не вдається вирішити сферу обслуговування від кореневого постачальника .Net Core 2


92

Коли я намагаюся запустити свій додаток, я отримую повідомлення про помилку

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

Дивно те, що цей EmailRepository та інтерфейс налаштовані точно так само, наскільки я можу зрозуміти, як і всі інші мої сховища, але жодної помилки для них не виникає. Помилка виникає лише в тому випадку, якщо я намагаюся використовувати додаток. 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, що змусило мене змінити метод BuildWebHost мого файлу Program.cs з цього

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, вони ввімкнули його за замовчуванням.
Роберт Берк,

Для тих , хто намагається відключити від ValidateScopes, будь ласка , прочитайте цю stackoverflow.com/a/50198738/1027250
Yorro

Відповіді:


188

Ви зареєстрували IEmailRepositoryпослугу як сферу дії у Startupкласі. Це означає, що ви не можете вставити його як параметр конструктора, 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);
    }
}

13
Оце Так! Ніколи не знав, що ти можеш вводити методи, це лише для проміжного програмного забезпечення, чи я можу використовувати цей фокус у своїх власних методах?
Фергал Моран

А як щодо IMiddleware, зареєстрованого як область дії? Я точно знаю, що отримую новий екземпляр проміжного програмного забезпечення, але все одно не можу додати йому сервіс із обмеженими можливостями.
Ботіс

2
@FergalMoran На жаль, цей "фокус" є особливою поведінкою лише Invokeметоду проміжного програмного забезпечення . Тим не менш, ви можете досягти чогось подібного за допомогою автофаку IoC lib та введення властивостей. Див. Ін’єкцію залежності ASP.NET Core MVC за допомогою властивості чи методу встановлення? .
B12Тостер

4
Ін’єкція - це не магія. Існує механізм, що за кадром фактично викликає контейнер залежностей, щоб генерувати екземпляри, які передаються як параметри конструкторам або методам. Цей конкретний механізм шукає методи з назвою "Invoke" з першим аргументом HttpContext, а потім створює екземпляри для решти параметрів.
Танасіс Іоаннідіс

98

Інший спосіб отримати екземпляр залежної області - це ввести постачальника послуг ( IServiceProvider) у конструктор проміжного програмного забезпечення, створити метод scopein, 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

_serviceProvider.CreateScope (). ServiceProvider робить для мене краще
XLR8

Я думаю, що IServiceScopeFactoryдля цього найкраще було б використати
Франческо Д.М.

27

Проміжне програмне забезпечення завжди є єдиним елементом, тому ви не можете мати залежності в межах сфери як залежності конструктора в конструкторі вашого проміжного програмного забезпечення.

Проміжне програмне забезпечення підтримує введення методу за допомогою методу Invoke, тому ви можете просто додати IEmailRepository emailRepository як параметр до цього методу, і він буде там введений і буде в порядку, як у масштабі.

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

    ....
}

Я був у подібній ситуації, тоді я додав службу за допомогою AddTransient, і вона змогла вирішити залежність. Я думав, що це не буде працювати, оскільки проміжне програмне забезпечення є одностороннім? трохи дивно ..
Сатеш Паголу

1
Я думаю, що перехідну залежність потрібно було б утилізувати вручну, на відміну від сфери, яка автоматично утилізується в кінці веб-запиту, де вона створена вперше. Можливо, перехідний одноразовий пристрій із зоною дії може бути утилізований, а зовнішній об'єкт - усунутий. Проте я не впевнений, що перехідна залежність всередині одиночного елемента або об'єкта, що перевищує перехідний термін життя, є хорошою ідеєю, думаю, я б цього уникав.
Джо Одетт,

2
Навіть незважаючи на те, що в цьому випадку ви можете ввести залежність із перехідним спектром за допомогою конструктора, вона не буде інстанційована, як ви думаєте. Це відбудеться лише один раз, коли побудується Сінглтон.
Джонатан,

1
Ви вже згадували, що проміжне програмне забезпечення завжди є єдиним, але це неправда. Можна створити проміжне програмне забезпечення як фабричне проміжне програмне забезпечення та використовувати його як проміжне ПЗ.
Харун Ділука Хешан,

Схоже, фабричне проміжне програмне забезпечення було введено в asp.netcore 2.2, а документацію створено в 2019 році. Тож моя відповідь була правдивою, коли я опублікував її, наскільки мені відомо. Заводське проміжне програмне забезпечення сьогодні виглядає як гарне рішення.
Джо

4

Ваш middlewareі the serviceповинні бути сумісними між собою, щоб вводити serviceчерез constructorваш middleware. Тут ваш сервер middlewareстворений як a, convention-based middlewareщо означає, що він діє як a, singleton serviceа ви створили свою послугу як scoped-service. Отже, ви не можете ввести a scoped-serviceв конструктор a, singleton-serviceоскільки він змушує scoped-serviceдіяти як єдине singletonціле. Однак, ось ваші варіанти.

  1. Введіть свою послугу як параметр InvokeAsyncметоду.
  2. Зробіть свою послугу односторонньою, якщо це можливо.
  3. Перетворіть свій middlewareна factory-basedодного.

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

The TestMiddleware:

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

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

The TestService:

public class TestService
{
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.