Исключить контекстную привязку для 2+ зависимых классов с разными именами для одного и того же параметра конструктора

Возникают проблемы с выяснением того, как управлять контекстной привязкой в ​​сценарии, где два класса имеют одинаковую базовую интерфейсную зависимость, но параметр ctor каждого класса назван по-разному. Псевдокод ниже, чтобы продемонстрировать мою ситуацию:

    interface IThing { }
    public class Thing1 : IThing { public Thing1(string fileCode) { } }
    public class Thing2 : IThing { public Thing2(string fileCode) { } }
    interface IThingFactory { IThing CreateThing(string fileCode); }

    interface IDependentThing { }
    public class A : IDependentThing { public A(string fileCode, IThingFactory thingFactory) { } }
    public class B : IDependentThing { public B(string fileCd, IThingFactory thingFactory) { } } //How to handle binding for this dependent?
    interface IDependentThingFactory { IDependentThing CreateDependentThing(string fileCode); }

    //...

    public override void Load()
    {
        Bind<IThing>().ToMethod(ctx =>
        {
            var fileCode = ctx.Parameters.First(p => p.Name == "fileCode").GetValue(ctx, null) as string;
            IThing thing = null;

            if (fileCode == "FileType1")
            {
                Bind<Thing1>().ToSelf().WithConstructorArgument("fileCode", fileCode);
                thing = Kernel.Get<Thing1>();
            }
            else if (fileCode == "FileType2")
            {
                Bind<Thing2>().ToSelf().WithConstructorArgument("fileCode", fileCode);
                thing = Kernel.Get<Thing2>();
            }
            return thing;
        });

        Bind<IThingFactory>().ToFactory();
        Bind<IDependentThingFactory>().ToFactory();
    }

//Later...
using (TextReader tr = new StreamReader(path))
{
    string firstLine = tr.ReadLine();

    if (firstLine.Substring(838, 1) == ".")
    {
        fileCode = "FileType1";
    }
    else if (firstLine.Substring(883, 1) == ".")
    {
        fileCode = "FileType2";
    }

    //won't work for creating B
    Kernel.Get<IDependentThing>(new ConstructorArgument("fileCode", fileCode));

    //or maybe...

    //seems to eliminate my problem by allowing me to handle variations
    //in parameter names  from within A and B's ctors, but looks like it
    //requires injecting factories along the chain (see A & B ctor arguments).
    dependentThingFactory.CreateDependentThing(fileCode) 
};

fileCode вычисляется на основе некоторого анализа локальных файлов. Как только тип файла определен, я хочу, чтобы Ninject вернул соответствующий объект для обработки этого файла.

Как бы я справился с переплетом для B поскольку для существующей привязки, которую я определил, требуется параметр конструктора с другим именем? Есть ли лучший способ сделать это в целом?

Я думаю, я мог бы просто использовать p.Name == "fileCode" || p.Name == "fileCd", но я не могу избавиться от ощущения, что я делаю что-то не так (чувствует себя грязно). Кроме того, я не в восторге от извлечения параметров по имени, и я подумал о том, чтобы, возможно, создать собственный тип, который дал бы Ninject что-то более конкретное для сопоставления с строковым параметром. С того места, где я стою, похоже, что я либо просто управляю ситуацией с несколькими именами параметров, либо переключаюсь на пользовательские типы в качестве своих параметров вместо строк.

1 ответ

Решение

Повышение безопасности при внедрении параметров и их доступность для всего контекста разрешения.

Вместо "именованных параметров" вы можете использовать "тип сопоставления" или "типизированный" параметр. Заводы IInstanceProvider можно обменять на другой, который делает это:

kernel.Bind<IThingFactory>()
      .ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());

Замечания:

  • этот IInstanceProvider также сделает аргумент доступным далее "downstream" (он "наследует" параметр)
  • string очень многословен, так что вы можете захотеть обернуть его в другой тип, например class ConnectionInfo,

Контекстная привязка в сочетании с внедрением параметров

Так скажем, мы создаем наш собственный FileType типа быть более многословным, чем просто используя string:

public class FileCode
{
    public FileCode(string value)
    {
        Value = value;
    }

    public string Value { get; private set; }
}

(возможно, вы хотите заменить это на enum?)

Поскольку ваши требования более сложны, нам придется немного изменить положение вещей. Мы собираемся создать свой собственный IConstructorArgument легко быть в состоянии соответствовать When-контекстовые привязки, а также вводят их значения на основе сопоставления типов (как указано выше):

internal class FileCodeParameter : IConstructorArgument
{
    private readonly FileCode fileCode;

    public FileCodeParameter(FileCode fileCode)
    {
        this.fileCode = fileCode;
    }

    public string Name { get { return "File Code Parameter"; } }

    public bool ShouldInherit { get { return true; } }

    public FileCode FileCode  { get { return this.fileCode; } }

    public bool Equals(IParameter other)
    {
        var otherFileCodeParameter = other as FileCodeParameter;
        if (otherFileCodeParameter == null)
        {
            return false;
        }

        return otherFileCodeParameter.fileCode == this.fileCode;
    }

    public object GetValue(IContext context, ITarget target)
    {
        return this.fileCode;
    }

    public bool AppliesToTarget(IContext context, ITarget target)
    {
        return target.Type == typeof(FileCode);
    }
}

Теперь позвольте мне создать несколько примеров кодов, чтобы позже мы могли убедиться, что это работает:

public interface IThing
{
    FileCode FileCode { get; }
}

public abstract class Thing : IThing
{
    protected Thing(FileCode fileCode)
    {
        FileCode = fileCode;
    }

    public FileCode FileCode { get; private set; }
}

public class ThingFoo : Thing
{
    public ThingFoo(FileCode fileCode) : base(fileCode) { }
}

public class ThingBar : Thing
{
    public ThingBar(FileCode fileCode) : base(fileCode) { }
}

public interface IOtherThing
{
    FileCode FileCode { get; }
}

public abstract class OtherThing : IOtherThing
{
    protected OtherThing(FileCode fileCode)
    {
        FileCode = fileCode;
    }

    public FileCode FileCode { get; private set; }
}

public class OtherThingFoo : OtherThing
{
    public OtherThingFoo(FileCode fileCode) : base(fileCode) { }
}

public class OtherThingBar : OtherThing
{
    public OtherThingBar(FileCode fileCode) : base(fileCode) { }
}

public class OtherThingWrapper
{
    public OtherThingWrapper(IOtherThing otherThing)
    {
        OtherThing = otherThing;
    }

    public IOtherThing OtherThing { get; private set; }
}

public class FileProcessor
{
    public FileProcessor(IThing thing, OtherThingWrapper otherThingWrapper)
    {
        Thing = thing;
        OtherThingWrapper = otherThingWrapper;
    }

    public IThing Thing { get; private set; }
    public OtherThingWrapper OtherThingWrapper { get; private set; }
}

Чего не хватает? Фабрика. Мы могли бы использовать ToFactory связывание с обычаем IInstanceProvider но если мы не собираемся создавать много заводов с FileCodeParameterя не думаю, что это имеет смысл, поэтому давайте сделаем это просто:

public interface IFileProcessorFactory
{
    FileProcessor Create(FileCode fileCode);
}

internal class FileProcessorFactory : IFileProcessorFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public FileProcessorFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public FileProcessor Create(FileCode fileCode)
    {
        return this.resolutionRoot.Get<FileProcessor>(new FileCodeParameter(fileCode));
    }
}

Теперь давайте все вместе:

public class Test
{
    [Fact]
    public void FactMethodName()
    {
        var fooFileCode = new FileCode("foo");
        var barFileCode = new FileCode("bar");

        var kernel = new StandardKernel();
        kernel
            .Bind<IFileProcessorFactory>()
            .To<FileProcessorFactory>();

        kernel
            .Bind<IThing>()
            .To<ThingFoo>()
            .WhenFileCode(fooFileCode);
        kernel
            .Bind<IThing>()
            .To<ThingBar>()
            .WhenFileCode(barFileCode);

        kernel
            .Bind<IOtherThing>()
            .To<OtherThingFoo>()
            .WhenFileCode(fooFileCode);
        kernel
            .Bind<IOtherThing>()
            .To<OtherThingBar>()
            .WhenFileCode(barFileCode);


        var fileProcessor = kernel.Get<IFileProcessorFactory>().Create(barFileCode);
        fileProcessor.Thing.Should().BeOfType<ThingBar>();
        fileProcessor.Thing.FileCode.Should().Be(barFileCode);
        fileProcessor.OtherThingWrapper.OtherThing.Should().BeOfType<OtherThingBar>();
        fileProcessor.OtherThingWrapper.OtherThing.FileCode.Should().Be(barFileCode);
    }
}

public static class BindingExtensionsForFileCodes
{
    public static IBindingInNamedWithOrOnSyntax<T> WhenFileCode<T>(
        this IBindingWhenSyntax<T> syntax,
        FileCode fileCode)
    {
        return syntax.When(req => req
            .Parameters
            .OfType<FileCodeParameter>()
            .Single()
            .FileCode.Value == fileCode.Value);
    }
}

Это оно! - FileCode одновременно вводится и используется для выбора реализации - поскольку параметр "наследуется", он также работает глубже в дереве объектов.

Ниже, просто для справки, весь код для удобства копирования и вставки:

using FluentAssertions;
using Ninject;
using Ninject.Activation;
using Ninject.Parameters;
using Ninject.Planning.Targets;
using Ninject.Syntax;
using System.Linq;
using Xunit;

namespace NinjectTest.ParameterContextual
{
    public class FileCode
    {
        public FileCode(string value)
        {
            Value = value;
        }

        public string Value { get; private set; }
    }

    public interface IThing
    {
        FileCode FileCode { get; }
    }

    public abstract class Thing : IThing
    {
        protected Thing(FileCode fileCode)
        {
            FileCode = fileCode;
        }

        public FileCode FileCode { get; private set; }
    }

    public class ThingFoo : Thing
    {
        public ThingFoo(FileCode fileCode) : base(fileCode) { }
    }

    public class ThingBar : Thing
    {
        public ThingBar(FileCode fileCode) : base(fileCode) { }
    }

    public interface IOtherThing
    {
        FileCode FileCode { get; }
    }

    public abstract class OtherThing : IOtherThing
    {
        protected OtherThing(FileCode fileCode)
        {
            FileCode = fileCode;
        }

        public FileCode FileCode { get; private set; }
    }

    public class OtherThingFoo : OtherThing
    {
        public OtherThingFoo(FileCode fileCode) : base(fileCode) { }
    }

    public class OtherThingBar : OtherThing
    {
        public OtherThingBar(FileCode fileCode) : base(fileCode) { }
    }

    public class OtherThingWrapper
    {
        public OtherThingWrapper(IOtherThing otherThing)
        {
            OtherThing = otherThing;
        }

        public IOtherThing OtherThing { get; private set; }
    }

    public class FileProcessor
    {
        public FileProcessor(IThing thing, OtherThingWrapper otherThingWrapper)
        {
            Thing = thing;
            OtherThingWrapper = otherThingWrapper;
        }

        public IThing Thing { get; private set; }
        public OtherThingWrapper OtherThingWrapper { get; private set; }
    }

    public interface IFileProcessorFactory
    {
        FileProcessor Create(FileCode fileCode);
    }

    internal class FileProcessorFactory : IFileProcessorFactory
    {
        private readonly IResolutionRoot resolutionRoot;

        public FileProcessorFactory(IResolutionRoot resolutionRoot)
        {
            this.resolutionRoot = resolutionRoot;
        }

        public FileProcessor Create(FileCode fileCode)
        {
            return this.resolutionRoot.Get<FileProcessor>(new FileCodeParameter(fileCode));
        }
    }

    public class Test
    {
        [Fact]
        public void FactMethodName()
        {
            var fooFileCode = new FileCode("foo");
            var barFileCode = new FileCode("bar");

            var kernel = new StandardKernel();
            kernel
                .Bind<IFileProcessorFactory>()
                .To<FileProcessorFactory>();

            kernel
                .Bind<IThing>()
                .To<ThingFoo>()
                .WhenFileCode(fooFileCode);
            kernel
                .Bind<IThing>()
                .To<ThingBar>()
                .WhenFileCode(barFileCode);

            kernel
                .Bind<IOtherThing>()
                .To<OtherThingFoo>()
                .WhenFileCode(fooFileCode);
            kernel
                .Bind<IOtherThing>()
                .To<OtherThingBar>()
                .WhenFileCode(barFileCode);


            var fileProcessor = kernel.Get<IFileProcessorFactory>().Create(barFileCode);
            fileProcessor.Thing.Should().BeOfType<ThingBar>();
            fileProcessor.Thing.FileCode.Should().Be(barFileCode);
            fileProcessor.OtherThingWrapper.OtherThing.Should().BeOfType<OtherThingBar>();
            fileProcessor.OtherThingWrapper.OtherThing.FileCode.Should().Be(barFileCode);
        }
    }

    internal class FileCodeParameter : IConstructorArgument
    {
        private readonly FileCode fileCode;

        public FileCodeParameter(FileCode fileCode)
        {
            this.fileCode = fileCode;
        }

        public string Name { get { return "File Code Parameter"; } }

        public bool ShouldInherit { get { return true; } }

        public FileCode FileCode  { get { return this.fileCode; } }

        public bool Equals(IParameter other)
        {
            var otherFileCodeParameter = other as FileCodeParameter;
            if (otherFileCodeParameter == null)
            {
                return false;
            }

            return otherFileCodeParameter.fileCode == this.fileCode;
        }

        public object GetValue(IContext context, ITarget target)
        {
            return this.fileCode;
        }

        public bool AppliesToTarget(IContext context, ITarget target)
        {
            return target.Type == typeof(FileCode);
        }
    }

    public static class BindingExtensionsForFileCodes
    {
        public static IBindingInNamedWithOrOnSyntax<T> WhenFileCode<T>(
            this IBindingWhenSyntax<T> syntax,
            FileCode fileCode)
        {
            return syntax.When(req => req
                .Parameters
                .OfType<FileCodeParameter>()
                .Single()
                .FileCode.Value == fileCode.Value);
        }
    }
}
Другие вопросы по тегам