【问题标题】:How to Inject multiple Implementations of an Interface into one Implementation如何将一个接口的多个实现注入到一个实现中
【发布时间】:2019-05-27 15:29:00
【问题描述】:

我正在尝试将IResourceService 的实现集合注入IResourceService 的另一个实现中,以便第一个实现能够在它无法对资源进行操作的情况下重定向到正确的服务。到目前为止,我尝试过的每一个实现都导致了循环依赖——所以我要么想知道如何解决这个问题,要么想知道如何设计它以使其不成问题。

我正在创建许多控制器,它们都负责不同的资源。我将简化这个问题的情况——但实际上我有一个PetController,它能够对所有提供 ID 的宠物进行简单的操作。例如,导航到/pet/1234 将为您提供宠物详细信息的摘要以及它是什么类型的宠物。

然后,我为每种宠物类型设置了多个控制器,即DogControllerCatControllerHamsterController。所有这些都继承自 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 的实现,其中resourceControllersIEnumerable&lt;IResourceController&gt;

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

【问题讨论】:

  • 我很想看看你对ResourceControllerSelector 的阐述,因为这对我来说似乎是正确的。如果要求IResourceController 的实例,我希望只解析一种类型。
  • 不一样,但这似乎与您正在尝试的相似:stackoverflow.com/a/53642638/991609
  • 是的,如果您不更改代码的结构,ResourceControllerSelector 似乎是正确的选择。很遗憾,由于我们不知道您的问题域,因此我们无法提供真正的帮助。
  • @KrikWoll 资源控制器选择器看起来像这样(我会将其添加到原始问题中)
  • @DavidG 我不知道如何将ResourceControllerSelector 注入控制器,也将控制器注入ResourceControllerSelector

标签: c# asp.net dependency-injection .net-core


【解决方案1】:

我认为这个问题有分歧,有问题本身(这是我第一次尝试解决我遇到的问题),然后是我遇到的问题。所以我想我会用我学到的东西来更新这个问题。

回答问题

简短的回答是这对于 ASP.Net 标准依赖注入是不可能的,它不够先进。但是,使用 Autofac 和/或 SimpleInjector 之类的东西,您可以进行条件注入或上下文驱动注入。在这个世界上,您可以说注入所有不符合特定条件的接口实现。因此,如果实现与您目前尝试创建的匹配,您可以说不要注入。我没有在愤怒中尝试过,但理论上这可以解决问题

解决我的问题

我想要这样做的原因是,如果您尝试在仓鼠上使用 DogController 执行操作,我可以返回准确的 SeeOther (303) 响应。在我看来,这意味着注入其他控制器,以便它可以查询它们的信息(即找到正确操作的 actionName 和 controllerName)。然而另一种方法是使用反射和属性来表示控制器负责什么。所以我改用这样的方法:

我像这样装饰每个资源控制器: [ResourceController(Type = typeof(Dog), ControllerName = "Dog")] 公共类 DogController : BasePetController

然后我在这里有一个名为 SeeOtherResultResolver 的帮助程序类,它扫描存储库以查找正确的控制器,然后扫描正确的 HttpGetHttpDelete 等属性。

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

感谢大家的帮助。我真的很感激,你让我意识到我有点走错树了!

【讨论】:

    【解决方案2】:

    我想我明白你的问题了。

    解决方案很简单,只需为每个宠物具体类型创建存根接口,它就会看起来像这样。每个控制器都会使用自己的资源。

    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 并将 Dog+Hamster 注入 Cat 等。但我希望自动完成(理论上,如果狗得到自己并不重要)

    因此,如果您想这样做,只需获取上面的代码并进行更改即可。不要使用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>();
    

    【讨论】:

    • 感谢您的回复,但这并不是我想要的 - 我在这里实际上想要的是注入 ICatResourceIHamsterResourceDogController 和 Dog+Hamster 到猫等。但我希望自动完成(理论上,如果狗自己得到它并不重要)
    • 我已经编辑了我的答案,请查看 lask 编辑。是你想要的吗?
    • 为更新干杯,我认为问题是我需要注入控制器本身,我认为我不能像这样将它分开,因为控制器需要访问的是 @987654327 @update 方法为例。我觉得我正在尝试解决一些不应该成为问题的问题
    • 我不填写你为什么要这样做,不应该这样做。决定你想要什么。
    • 如果它来自正确的设计角度,我认为你有两个选择。 1. 为具体的宠物创建许多控制器(因此您将有许多端点猫,宠物控制器),每个控制器都有自己的资源实现。 2. 为“pet”创建一个控制器并添加层,该层将确定用户的资源现在我填写了您想要两者都拥有但添加的内容,是不是有点错误?我不认为向控制器注入另一个控制器是个好主意,除了你所看到的不可能。可以更准确地解释一下你的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 2014-01-24
    • 1970-01-01
    • 2014-11-18
    • 1970-01-01
    • 2022-01-15
    相关资源
    最近更新 更多