Как внедрить несколько реализаций интерфейса в одну реализацию - PullRequest
0 голосов
/ 31 декабря 2018

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

Ясоздание ряда контроллеров, которые присматривают за разными ресурсами.Я упрощу для случая этого вопроса - но на самом деле у меня есть PetController, который может выполнять простые операции с домашними животными, для которых все имеют идентификаторы.Так, например, переход к /pet/1234 даст вам сводную информацию о питомце и типе этого питомца.

Затем у меня будет несколько контроллеров для каждого типа питомца, т.е. DogController, CatControllerи HamsterController.Все они наследуются от BasePetController, который фактически выполняет всю тяжелую работу.В сценарии, где вы выполняете операцию над /dog/1234, но PetId 1234 фактически соответствует хомяку, я хочу, чтобы DogController возвратил соответствующий ответ (перенаправление при получении, конфликт при обновлении и т. Д.).

Итак, у меня есть что-то вроде этого:

BasePetController : IResourceController
{
    BasePetController(IResourceController[] resourceControllers){}

    abstract Type SupportedResourceType { get; }
}

DogController : BasePetController
{
     DogController(IResourceController[] resourceControllers) : base(resourceControllers) {}

     Type SupportedResourceType => typeof(Dog);
}

Это затем добавляется к DI следующим образом:

services.AddSingleton<IResourceController, DogController>();
services.AddSingleton<IResourceController, CatController>(); // etc.

Это приводит к циклической зависимости, потому что DogController нуждается в себе в порядкечтобы создать себя эффективно.

Я попытался ввести ResourceControllerSelector, чтобы отделить этот конфликт немного дальше, но без кубиков.

Я хочу, чтобы контроллер ушел (В псевдоКод)

If (can't update dog because it isn't a dog) {
    controller = getResourceControllerForPet()
    action = controller.GetActionForUpdateOperation()
    return information for the user to go to the correct action
}

Я думаю, что отдельные контролеры домашних животных должны нести ответственность за знание того, может ли это обрабатывать определенный ресурс, а не за другую службу, которая знает всю эту информацию + какие действия вызывать и т. Д. Но можетне могу понять, как дать каждому питомцу возможность самостоятельно получать эту информацию и / или на уровне API, не создавая циклическую зависимость.

Любая помощь по этому вопросу будет принята с благодарностью.Будь то помощь в решении проблемы внедрения зависимостей, дизайн требуемых сервисов или даже сам API.

Добавление реализации ResourceControllerSelector, где resourceControllers - это IEnumerable<IResourceController>.

public IResourceController SelectController(Type resourceType)
{
    EnsureArg.IsNotNull(resourceType, nameof(resourceType));

    return this.resourceControllers.SingleOrDefault(x => x.SupportedResourceType == resourceType)
        ?? throw new ArgumentOutOfRangeException(nameof(resourceType), resourceType, "No resource controller has been configured to handle the provided resource.");
}

Ответы [ 2 ]

0 голосов
/ 02 января 2019

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

Отвечая на вопрос

Короткий ответ: это невозможно при внедрении стандартной зависимости ASP.Net, это не так.Я достаточно продвинут.Однако с такими вещами, как Autofac и / или SimpleInjector, вы можете делать условные или контекстно-зависимые инъекции.В этом мире вы можете сказать, внедрить все реализации интерфейса, которые не соответствуют определенным критериям.Таким образом, вы можете сказать, не вводите, если реализация соответствует тому, что вы пытаетесь создать в данный момент.Я не пробовал это в гневе, но теоретически это могло бы обойти проблему

Решение моей проблемы

Причина, по которой я хотел это, состояла в том, чтобы я мог вернуть точное значение SeeOther (303)ответы, если вы пытались выполнить действие, используя DogController на хомяке.По-моему, это означало внедрение других контроллеров, чтобы он мог запрашивать у них информацию (то есть найти actionName и controllerName правильного действия).Однако другой способ заключается в использовании отражения и атрибутов для обозначения того, за что отвечает контроллер.Поэтому вместо этого я использовал что-то вроде этого:

Я декорирую каждый контроллер ресурсов следующим образом: [ResourceController (Type = typeof (Dog), ControllerName = "Dog")] открытый класс DogController: BasePetController

И тут у меня есть вспомогательный класс, называемый SeeOtherResultResolver, который сканирует репозиторий на предмет правильного контроллера, а затем на правильный атрибут HttpGet или HttpDelete и т. Д.

private static readonly IDictionary<string, Type> AttributeMap
     = new Dictionary<string, Type>
     {
         { "GET", typeof(HttpGetAttribute) },
         { "DELETE", typeof(HttpDeleteAttribute) },
         { "PUT", typeof(HttpPutAttribute) },
     };

public string ResolveForSeeOtherResult(IUrlHelper urlHelper, object resource, object routeValues = null)
{
    string scheme = urlHelper.ActionContext.HttpContext.Request.Scheme;
    var httpAttribute = AttributeMap[urlHelper.ActionContext.HttpContext.Request.Method];

    var resourceControllerTypes = resource.GetType().Assembly.GetTypes()
            .Where(type => type.IsDefined(typeof(ResourceControllerAttribute), false));

    foreach (var type in resourceControllerTypes)
    {
        var resourceControllerAttribute = type.GetCustomAttributes(false).OfType<ResourceControllerAttribute>().Single();

        if (resourceControllerAttribute.Value == resource.GetType())
        {
            var methodInfo = type.GetMethods().Where(x => x.IsDefined(httpAttribute, false)).Single();

            var result = urlHelper.Action(methodInfo.Name, resourceControllerAttribute.ControllerName, routeValues, scheme);

            return result;
         }
    }

    var errorMessage = string.Format(ErrorMessages.UnrecognisedResourceType, resource.GetType());

    throw new InvalidOperationException(errorMessage);
}

Спасибо за помощь всем,Я действительно ценю это, вы заставили меня понять, что я немного лаю не на том дереве!

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

Я думаю, я понял вашу проблему.

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

public DogController {
public DogController(IDogResource dogResource)
}

public CatController {
   public CatController(ICatResource dogResource)
}

public interface IDogResource : IResource
{
}

public interface ICatResource : IResource
{
}

public interface IResource{
  //your common logic
}

services.AddSingleton<DogResource, IDogResource>();
services.AddSingleton<CatResource, ICatResource>();

** редактировать в случае, если у вас есть какая-то общая логика в базовом контроллере, вам нужно изменить приведенный выше код для этой формы, а остальное - то же самое

public BasePetController  {
  protected IResource resource;
  public BasePetController (IResource resource)
  {
      this.resource = resource;
  }
}

public DogController : BasePetController 
{
  public DogController(IDogResource dogResource): base (dogResource)
}

public CatController  : BasePetController
{
  public CatController(ICatResource dogResource): base (dogResource)
}

** еще одно редактирование

Спасибо за ответ, но это не совсем то, что я искал - здесь я действительно хочу внедрить ICatResource и IHamsterResource в DogControllerи Собака + Хомяк в Кошку и т. д. Но я хотел, чтобы это было сделано автоматически (теоретически не имеет значения, получит ли Собака себя)

Так что, если вы хотите сделать что-то подобноепросто возьмите приведенный выше код и измените его.Не используйте конкретные типы интерфейса, такие как IDogResource и т. Д., Используйте только IResource, и код будет выглядеть следующим образом

public class BasePetController  {
  protected IResource[] resources;
  public BasePetController (IResource[] resources)
  {
    this.resources = resources;
  }
}

public class DogController : BasePetController 
{
  public DogController(IResource[] resources): base (resources)
}

public class CatController  : BasePetController
{
  public CatController(IResource[] resources): base (resources)
}

public class DogResource : IResource 
{

}
public class DogResource : IResource 
{

}
public interface IResource{

}

services.AddSingleton<DogResource, IResource>();
services.AddSingleton<CatResource, IResource>();
...