Сервисный локатор ASP.NET Core 2.1 с простым инжектором, возвращающим ноль - PullRequest
0 голосов
/ 05 декабря 2018

У меня есть приложение .NET MVC 5 .NET Framework, которое я конвертирую в .NET Core 2.1

У меня есть фильтр настраиваемых действий, который в версии .NET Framework был зарегистрирован как глобальный фильтр в Filterconfigкласс как показано ниже:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new MyCustomActionFilter());
    }
}

В фильтре настраиваемых действий в версии .NET я использовал шаблон локатора службы (я знаю, что это можно рассматривать как шаблон), как показано ниже:

var myService = DependencyResolver.Current.GetService<IMyService>();

Я использую Simple Injector для DI, и все отлично работает в версии .NET.В версии .NET Core я пытаюсь заставить работать ту же функциональность, но myService всегда имеет значение null

Я все еще использую Simple Injector (так как все другие проекты в решении используют его, и они не переходят кПроекты .NET Core (только веб).

Мой класс Startup.cs имеет следующий код:

services.Configure<MvcOptions>(options =>
{
    options.Filters.Add(new MyCustomActionFilter());
});

SimpleInjectorConfig.IntegrateSimpleInjector(services, container);

На моем уровне обслуживания у меня есть класс SimpleInjector Registartion, который вызывается изВеб-слой - затем вызывается до уровня DAL для регистрации

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container)
    {
        container.Register<IMyService, MyService>();
        //further code removed for brevity

Когда я запускаю приложение с точкой останова в пользовательском фильтре и точкой останова в этом методе RegisterServices, я вижу точку останова в методе RegisterServicesСначала получают удар, а затем точку останова в пользовательском фильтре - это заставляет меня думать, что все было правильно подключено в контейнере.

Однако я пытаюсь сделать следующее снова в пользовательском фильтре с .NET Core Service Locatorpattern

var myService = filterContext.HttpContext.RequestServices.GetService<IMyService>();

но результат всегда нулевой?

Есть ли такЧто я пропустил в этой настройке?

------------ ОБНОВЛЕНИЕ -------------------

На основе комментария Стивенса я добавил конструктор в свой фильтр действий и передал в контейнер Simple Injector.

Итак, класс My Startup теперь имеет вид:

public class Startup
{
    //Simple Injector container
    private Container container = new Container(); 

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();


        services.Configure<MvcOptions>(options =>
        {
           options.Filters.Add(new MyCustomActionFilter(container)); 

Мой пользовательский фильтр теперь похож на приведенный ниже конструктор:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly IMyService _myService;

    public MyCustomActionFilter(Container container)
    {
        _myService = container.GetService<IMyService>();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
       //actual code of custom filter removed - use of MyService 

Я установил точку останова на конструкторе MyCustomActionFilter, и я вижу, что он получает удар, но я получаю ошибку:

SimpleInjector.ActivationException: 'IDbContext зарегистрирован как образ жизни с асинхронной областью действия, но экземпляр запрашивается вне контекста активной области (асинхронной области).'

MyService имеет зависимость отDbContext, который вводится в него (он выполняет работу, сохраняя и извлекая данные из БД.

Для контекста БД я зарегистрировал его следующим образом:

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container, string connectionString)
    {
        container.Register<IDbContext>(() => new MyDbContext(connectionString),
            Lifestyle.Scoped);
    }

}

Ответы [ 3 ]

0 голосов
/ 06 декабря 2018

Существуют некоторые значительные изменения между тем, как интегрировать Simple Injector в старый ASP.NET MVC и новое ядро ​​ASP.NET.В старой системе вы могли бы заменить IDependencyResolver.Однако ASP.NET Core содержит совершенно другую модель со своим собственным внутренним DI-контейнером.Поскольку невозможно заменить этот встроенный контейнер на Simple Injector, вы будете иметь два контейнера, работающих бок о бок.В этом случае встроенный контейнер разрешит каркас и сторонние компоненты, где Simple Injector будет составлять компоненты приложения для вас.

Когда вы вызываете HttpContext.RequestServices.GetService, вы будете запрашивать встроенный контейнер длясервис, не Простой инжектор.Добавление регистрации IMyService во встроенный контейнер, как предполагает ответ TanvirArjel, поначалу может показаться работающим, но это полностью пропускает Simple Injector из уравнения, что, очевидно, не вариант, так как вы хотите использовать Simple Injector какКонтейнер вашего приложения.

Чтобы имитировать поведение, подобное сервисному локатору, которое вы использовали ранее, вам нужно будет вставить SimpleInjector.Container в ваш фильтр следующим образом:

options.Filters.Add(new MyCustomActionFilter(container));

Это будетоднако ошибка при вызове контейнера из конструктора, как показано в вашем вопросе:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly IMyService _myService;

    public MyCustomActionFilter(Container container)
    {
        _myService = container.GetService<IMyService>(); // NEVER DO THIS!!!
    }

    ...
}

ВНИМАНИЕ: никогда не разрешать изконтейнер от конструктора.Или в более общем смысле: вы никогда не должны использовать какие-либо внедренные зависимости внутри конструктора.Конструктор должен хранить только зависимость.

Как объяснил Марк Симанн, конструкторы инъекций должны быть простыми .В этом случае это даже ухудшается, потому что:

  • В то время, когда вызывается конструктор MyCustomActionFilter, нет активной области, и IMyService не может быть разрешена
  • Даже если IMyService может быть разрешено, MyCustomActionFilter является Singleton, а сохранение IMyService в приватном поле приведет к скрытой зависимой зависимости .Это может привести к всевозможным проблемам.

Вместо сохранения разрешенной зависимости IMyService следует хранить зависимость Container:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly Container _container;

    public MyCustomActionFilter(Container container)
    {
        _container = container;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        myService = container.GetService<IMyService>();
        //actual code of custom filter removed - use of MyService 
    }
}

В течение временичто OnActionExecuting вызывается, будет активный простой инжектор Scope, который позволит разрешить IMyService.Кроме того, поскольку IMyService не хранится в закрытом поле, оно не будет кэшировано и не вызовет зависимостей в неволе.

В своем вопросе вы ссылались на антивирусную программу Service Locator.рисунок .Является ли введение Container в ваш фильтр на самом деле реализацией антишаблона Service Locator, зависит от того, где расположен фильтр.Как отмечает Марк Симанн , это:

Контейнер DI, инкапсулированный в корень композиции, не является локатором службы - это компонент инфраструктуры.

InДругими словами, если класс фильтра расположен внутри вашего Корня композиции , вы не применяете анти-шаблон Service Locator.Это, однако, означает, что вы должны убедиться, что сам фильтр содержит как можно меньше интересного поведения.Это поведение должно быть перенесено в службу, которую разрешает фильтр.

0 голосов
/ 06 декабря 2018

Как указывает @Steven, встроенный контейнер будет разрешать компоненты инфраструктуры и сторонние компоненты, где Simple Injector будет составлять компоненты приложения для вас.Для встроенного контейнера он не мог разрешить службу из простого инжектора.Для простого инжектора вы можете попробовать EnableSimpleInjectorCrossWiring для разрешения служб из встроенного контейнера.

Для options.Filters.Add он также принимает MyCustomActionFilter instance, не перенося Container в качестве зависимости в MyCustomActionFilter, вы можете попробовать зарегистрировать MyCustomActionFilter в образце-инжекторе, а затем передать этот экземпляр в options.Filters.Add,

  • Регистрация служб

        private void InitializeContainer(IApplicationBuilder app)
    {
        // Add application presentation components:
        container.RegisterMvcControllers(app);
        container.RegisterMvcViewComponents(app);
    
        // Add application services. For instance:
        container.Register<IMyService, MyService>(Lifestyle.Scoped);
        container.Register<MyCustomActionFilter>(Lifestyle.Scoped);
        // Allow Simple Injector to resolve services from ASP.NET Core.
        container.AutoCrossWireAspNetComponents(app);
    }
    
  • добавить MyCustomActionFilter

            services.Configure<MvcOptions>(options =>
        {
            using (AsyncScopedLifestyle.BeginScope(container))
            {
                options.Filters.Add(container.GetRequiredService<MyCustomActionFilter>());
            }
        });
        #region SampleInjector
        IntegrateSimpleInjector(services);
        #endregion
    

    Примечание Если указатьcontainer.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();, вам понадобится using (AsyncScopedLifestyle.BeginScope(container)) при звонке container.GetRequiredService<MyCustomActionFilter>().

0 голосов
/ 05 декабря 2018

В ASP.NET Core необходимо зарегистрировать службы в методе ConfigureServices() класса Startup следующим образом:

public void ConfigureServices(IServiceCollection services)
{
   services.AddScoped<IMyService, MyService>();
   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Попробуйте, надеюсь, ваша проблема исчезнет.

...