简体   繁体   中英

Using DI with ninject when interface has signature of other interfaces

I have an interface called IXmlStrategy which has declared another Interface like this:

namespace IceServices.Business.Interfaces.Strategies
{
    public interface IXmlStrategy
    {
        IGetPersonNameXmlStrategy GetPersonNameXmlStrategy { get; }
    }
}

I expect there to be many strategy interfaces and I want to put all of them in once interface so I don't have to inject 10-20 IXmlStrategy variants.

This is my class using DI:

using System.Linq;
using IceServices.Business.Interfaces.Commands;
using IceServices.Business.Interfaces.Configurations;
using IceServices.Business.Interfaces.Deserializers;
using IceServices.Business.Interfaces.Mappers;
using IceServices.Business.Interfaces.Services;
using IceServices.Business.Interfaces.Strategies;
using IceServices.DomainObjects.ResponseObjects;

namespace IceServices.Business.Commands
{
    public class IpdCommands : IIpdCommands
    {
        private readonly ISoapService _soapService;
        private readonly IUrlConfigurations _urlConfigurations;
        private readonly IXmlDeserializer _xmlDeserializer;
        private readonly IDomainObjectMapper _domainObjectMapper;
        private readonly IXmlStrategy _xmlStrategy;

        public IpdCommands(ISoapService soapService, IUrlConfigurations urlConfigurations, IXmlDeserializer xmlDeserializer, IDomainObjectMapper domainObjectMapper, IXmlStrategy xmlStrategy)
        {
            _soapService = soapService;
            _urlConfigurations = urlConfigurations;
            _xmlDeserializer = xmlDeserializer;
            _domainObjectMapper = domainObjectMapper;
            _xmlStrategy = xmlStrategy;
        }

        public PersonNameResponse GetPersonName(int id)
        {
            var webServiceXmlString = _xmlStrategy.GetPersonNameXmlStrategy.GetXml(id.ToString());
            var listOfProperties = typeof(PersonNameResponse).GetProperties().Select(expr => expr.Name);

            var xmlStringResponse = _soapService.CallWebService(_urlConfigurations.GetPersonFirstname, webServiceXmlString);
            var stringArrayOfProperties = listOfProperties as string[] ?? listOfProperties.ToArray();
            var dictionaryData = _xmlDeserializer.DeserializeXmlToDictionary(stringArrayOfProperties, xmlStringResponse);
            return _domainObjectMapper.MapDictionaryToObject<PersonNameResponse>(dictionaryData, stringArrayOfProperties);
        }
    }
}

as you can see I'm calling GetPersonNameXmlStrategy.GetXml(id) like this:

  var webServiceXmlString = _xmlStrategy.GetPersonNameXmlStrategy.GetXml(id.ToString());

However I get a runtime error because IOC doesn't understand how to resolve it's dependency:

{"Message":"An error has occurred.","ExceptionMessage":"An error occurred when trying to create a controller of type 'IpdController'. Make sure that the controller has a parameterless public constructor.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\\r\\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\\r\\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Error activating IXmlStrategy using binding from IXmlStrategy to XmlStrategy\\r\\nNo constructor was available to create an instance of the implementation type.\\r\\n\\r\\nActivation path:\\r\\n 3) Injection of dependency IXmlStrategy into parameter xmlStrategy of constructor of type IpdCommands\\r\\n 2) Injection of de pendency IIpdCommands into parameter ipdCommands of constructor of type IpdController\\r\\n 1) Request for IpdController\\r\\n\\r\\nSuggestions:\\r\\n 1) Ensure that the implementation type has a public constructor.\\r\\n 2) If you have implemented the Singleton pattern, use a binding with InSingletonScope() instead.\\r\\n","ExceptionType":"Ninject.ActivationException","StackTrace":" at Ninject.Activation.Providers.StandardProvider.Create(IContext context)\\r\\n at Ninject.Activation.Context.ResolveInternal(Object scope)\\r\\n at Ninject.Activation.Context.Resolve()\\r\\n at System.Linq.Enumerable.WhereSelectEnumerableIterator 2.MoveNext()\\r\\n at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable 1 source)\\r\\n at System.Linq.Enumerable.WhereSelectArrayIterator 2.MoveNext()\\r\\n at System.Linq.Buffer 1..ctor(IEnumerable 1 source)\\r\\n at System.Linq.Enumerable.ToArray[TSource](IEnumerable 1 source)\\r\\n at Ninject.Activation.Providers.StandardProvider.Create(IContext context)\\r\\n at Ninject.Activat ion.Context.ResolveInternal(Object scope)\\r\\n at Ninject.Activation.Context.Resolve()\\r\\n at System.Linq.Enumerable.WhereSelectEnumerableIterator 2.MoveNext()\\r\\n at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable 1 source)\\r\\n at System.Linq.Enumerable.WhereSelectArrayIterator 2.MoveNext()\\r\\n at System.Linq.Buffer 1..ctor(IEnumerable 1 source)\\r\\n at System.Linq.Enumerable.ToArray[TSource](IEnumerable 1 source)\\r\\n at Ninject.Activation.Providers.StandardProvider.Create(IContext context)\\r\\n at Ninject.Activation.Context.ResolveInternal(Object scope)\\r\\n at Ninject.Activation.Context.Resolve()\\r\\n at System.Linq.Enumerable.WhereSelectEnumerableIterator 2.MoveNext()\\r\\n at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable 1 source)\\r\\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\\r\\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpReq uestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"}}

This is my IOC setup in Ninject.WebCommon:

/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IIpdCommands>().To<IpdCommands>();
    kernel.Bind<IUrlConfigurations>().To<UrlConfigurations>();
    kernel.Bind<IXmlDeserializer>().To<XmlDeserializer>();
    kernel.Bind<IXmlDocumentFactory>().To<XmlDocumentFactory>();
    kernel.Bind<IDomainObjectMapper>().To<DomainObjectMapper>();
    kernel.Bind<ISoapService>().To<SoapService>();

    kernel.Bind<IXmlStrategy>().To<XmlStrategy>();
    kernel.Bind<IGetPersonNameXmlStrategy>().To<GetPersonNameXmlStrategy>();
}  

I need to change how the binding works for IGetPersonNameXmlStrategy but I'm not sure how. Does anyone know how I can get Ninject to resolve the depedency through IXmlStrategy.IGetPersonNameXmlStrategy ?

I expect there to be many strategy interfaces and I want to put all of them in once interface so I don't have to inject 10-20 IXmlStrategy variants.

Don't do this. What you are doing is described as a code smell here :

Service abstractions should not expose other service abstractions in their definition

In your case, your IXmlStrategy service abstraction exposes the IGetPersonNameXmlStrategy abstraction from its definition.

put all of them in once interface so I don't have to inject 10-20 IXmlStrategy variants.

This is a Interface Segregation Principle (ISP) violation. ISP states that:

no client should be forced to depend on methods it does not use.

Having such wide interface will cause a few things:

  • The interface will keep growing, which forces you to update the existing implementations as well.
  • It complicates unit testing, because it becomes unclear what dependencies a class under test uses.
  • When you keep adding services to that interface, the interface becomes a form of a Service Locator .

Instead you should simply let a consumer depend on the service that it requires. Not on the abstraction over the abstraction. In your specific case, inject the IGetPersonNameXmlStrategy directly into the consumer that requires it.

The most likely reason for you to have this Service Locator-like abstraction is because you are violating the Single Responsibility Principle (SRP). This principle states that classes should be focussed on one particular job, and only one requirement should cause them to change. When a class has many dependencies, it is a good indication that SRP is violated.

Classes that violate SRP tend to have constructors with many dependencies. This is called constructor over-injection. Constructor over-injection however is a symptom. You can fix over-injection by grouping dependencies, but that won't solve the root cause. The root problem is that the class is too complex and grouping together those dependencies won't fix this problem.

Instead a refactoring should take place. For instance, you can extract all the code that you have in a single method into a class of its own. This leaves you with just one dependency that can be injected in the original class. When you do this with all methods, you might even be able to remove the original class altogether, because it might now have lost its meaning.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM