Разрешить реализацию службы из Autofac на основе значения сеанса во время выполнения

Нужна помощь в попытке решить проблему с реализацией службы во время выполнения на основе параметра. Другими словами используйте фабричный образец с DI.

Мы подключили Autofac к нашему приложению MVC. Я пытаюсь выяснить, как мы можем использовать переменную сеанса пользователя (назовите ее Ordering Type), которая будет использоваться для Resoldency Resolver для решения правильной реализации службы.

Пример того, что мы пытаемся сделать.

Приложение имеет два "типа" заказа - реальный тип заказа электронной коммерции (добавление товара в корзину, оформление заказа и т. Д.).

Другой называется Прогнозирование. Пользователи создают заказы - но они не выполняются сразу. Они проходят процесс утверждения и затем выполняются.

Нижняя строка - это схема данных и серверные системы, с которыми приложение обращается к изменениям в зависимости от типа заказа.

Что я хочу сделать, это:

  1. У меня есть IOrderManagerService

    public interface IOrderManagerService
    {
          Order GetOrder(int orderNumber);
          int CreateOrder(Order order);
    }
    
  2. Поскольку у нас есть два упорядочивающих "типа" - у меня есть две реализации IOrderManagerService:

    public class ShelfOrderManager : IOrderManagerService
    {
        public Order GetOrder(int orderMumber)
        {
             ...code
        }
    
        public int CreateOrder(Order order)
        {
            ...code
        }
    }
    

а также

    public class ForecastOrderManager: IOrderManagerService
    {
        public Order GetOrder(int orderMumber)
        {
             ...code
        }

        public int CreateOrder(Order order)
        {
            ...code
        }
    }
  1. Мой первый вопрос - в моем приложении MVC - я регистрирую эти реализации как?

    builder.RegisterType<ShelfOrderManager>().As<IOrderManagerService>();
    builder.RegisterType<ForecastOrderManager>().As<IOrderManagerService>();
    
  2. Мы планируем придерживаться выбранного пользователем типа заказа в сеансе пользователей. Когда пользователь хочет просмотреть статус заказа - в зависимости от выбранного типа "заказа" - мне нужен распознаватель, чтобы дать контроллеру правильную реализацию.

    public class OrderStatusController : Controller
    {
          private readonly IOrderManagerService _orderManagerService;
    
          public OrderStatusController(IOrderManagerService orderManagerService)
          {
               //This needs to be the correct implementation based on the users "type".  
               _orderManagerService = orderManagerService; 
          }
    
          public ActionResult GetOrder(int orderNumber)
          {
               var model = _orderManagerService.GetOrder(orderNumber);
               return View(model);
          }
    }
    

Я готов о фабрике делегатов, и этот ответ хорошо объясняет концепцию.

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

     var service = resolvedServiceClass.Factory("runtime parameter")

Все, что нужно сделать, это дать мне "сервис", который использовал "параметр времени выполнения" в конструкторе.

Я также посмотрел на Keyed или Named разрешение.

Сначала я думал, что смогу объединить эти два метода - но контроллер зависит от интерфейса, а не от конкретной реализации. (как это должно)

Любые идеи о том, как обойти это было бы очень ценным.

2 ответа

Решение

Как оказалось, мы были близки. @Andrei нацелен на то, что мы сделали. Я объясню ответ ниже для следующего человека, который сталкивается с этой проблемой.

Чтобы вспомнить проблему - мне нужно было решить конкретную конкретную реализацию интерфейса, используя Autofac во время выполнения. Обычно это решается с помощью Factory Pattern, но мы уже реализовали DI.

Решение было использовать оба. Используя фабрику делегатов Autofac, я создал простой фабричный класс.

Я решил разрешить контекст компонента в частном порядке

   DependencyResolver.Current.GetService<IComponentContext>();

вместо того, чтобы Autofac разрешал его преимущественно, поэтому мне не пришлось включать IComponentContext во все мои конструкторы, которые будут использовать фабрику.

Фабрика будет использоваться для разрешения служб, которые зависят от параметров времени выполнения, что означает

  ISomeServiceThatHasMultipleImplementations 

используется в конструкторе - я собираюсь заменить его на фабрику ServiceFactory.Factory. Я не хотел ТАКЖЕ включать IComponentContext, где бы мне ни понадобилась фабрика.

enum OrderType 
{
    Shelf,
    Forecast
 }

public class ServiceFactory : IServiceFactory
{
    private readonly IComponentContext _componentContext;
    private readonly OrderType _orderType;
    public ServiceFactory(OrderType orderingType)
    {
        _componentContext = DependencyResolver.Current.GetService<IComponentContext>();
        _orderType = orderingType;
    }

    public delegate ServiceFactory Factory(OrderType orderingType);

    public T Resolve<T>()
    {
        if(!_componentContext.IsRegistered<T>())
            return _componentContext.ResolveNamed<T>(_orderType.ToString());

        return _componentContext.Resolve<T>();
    }
}

С фабрикой написано, мы также использовали сервисы Keyed.

Используя мой контекст заказа -

public interface IOrderManagerService
{
    Order GetOrder(int orderNumber);

    int CreateOrder(Order order);
}

public class ShelfOrderManager : IOrderManagerService
{
    public Order GetOrder(int orderNumber)
    {
        ...
    }

    public int CreateOrder(Order order)
    {
        ...
    }
}

public class ForecastOrderManager : IOrderManagerService
{
    public Order GetOrder(int orderNumber)
    {
        ...
    }

    public int CreateOrder(Order order)
    {
       ...
    }
}

Регистрация услуг Keyed:

        //register the shelf implementation
        builder.RegisterType<ShelfOrderManager>()
            .Keyed(OrderType.Shelf)
            .As<IOrderManager>();

        //register the forecast implementation
        builder.RegisterType<ForecastOrderManager>()
            .Keyed(OrderType.Shelf)
            .As<IOrderManager>();

Зарегистрировать фабрику:

 builder.RegisterType<IMS.POS.Services.Factory.ServiceFactory>()
            .AsSelf()
            .SingleInstance();

Наконец, используя его в контроллерах (или в любом другом классе):

public class HomeController : BaseController
{
    private readonly IContentManagerService _contentManagerService;
    private readonly IViewModelService _viewModelService;
    private readonly IApplicationSettingService _applicationSettingService;
    private readonly IOrderManagerService _orderManagerService;
    private readonly IServiceFactory _factory;

    public HomeController(ServiceFactory.Factory factory,
                                    IViewModelService viewModelService, 
                                    IContentManagerService contentManagerService, 
                                    IApplicationSettingService applicationSettingService)
    {
        //first assign the factory
        //We keep the users Ordering Type in session - if the value is not set - default to Shelf ordering
        _factory = factory(UIUserSession?.OrderingMode ?? OrderType.Shelf);

        //now that I have a factory to get the implementation I need
        _orderManagerService = _factory.Resolve<IOrderManagerService>();

        //The rest of these are resolved by Autofac
        _contentManagerService = contentManagerService;
        _viewModelService = viewModelService;
        _applicationSettingService = applicationSettingService;

    }
}

Я хочу немного больше обработать метод Resolve - но для первого прохода это работает. Немного Factory Pattern (там, где он нам нужен), но все еще использующий Autofac для выполнения большей части работы.

Я бы не стал полагаться на Autofac для этого. IOC используется для разрешения зависимости и обеспечения ее реализации. Вам нужно вызвать другую реализацию того же интерфейса на основе флага принятия решения.

Я бы в основном использовал простую фабрику, например, класс с двумя статическими методами, и вызывал бы ту реализацию, которая вам нужна, когда вы знаете, какое решение принимается. Это дает вам средство разрешения времени выполнения, которое вы ищете. Проще говоря, я бы сказал.

При этом, кажется, есть другой вариант. Взгляните на опцию "выбрать по контексту", возможно, вы можете изменить дизайн своих классов, чтобы воспользоваться этим: http://docs.autofac.org/en/latest/faq/select-by-context.html

Другие вопросы по тегам