0

I have a constructor like this that I would like to pass arguments to from a factory:

public Event(IRegisteredType registeredEarly, IPassNow passNowByInterface,
                               DateTimeOffset local, DateTimeOffset world)
{
    // ...
}

I tried this approach, but it did not work, because both local and world values where the same, one overwrote the other:

public Event CreateEvent(IPassNow passNow, DateTimeOffset local, DateTimeOffset world)
{
    var args = new Arguments { { typeof(IPassNow), passNow } };
    args.InsertTypedCollection(new object[] { local, world });
    return _container.Resolve<Event>(args);
}
  • registeredEarly should be resolved automatically, it is registered at composition root, and that works
  • passNowByInterface should be passed at resolution-time, but because it is an interface I need to specify the interface type, because otherwise Castle Windsor will try to use the concrete type of the argument, again that works - see Failure to pass generic arguments with Castle Windsor
  • local and world need to be passed at resolution-time as well, but note they are both the same concrete type and I cannot rely on variable names - see Resolution-time arguments of same type in Castle Windsor
  • I cannot get the last to work with the rest?
Community
  • 1
  • 1
Cel
  • 6,467
  • 8
  • 75
  • 110

3 Answers3

2

Resolve method has several overloads. Arguments type is simply IDictionary therefore keys cannot be the same.

If you try to create an instance of Arguments using its constructor you'll get a 'System.ArgumentException' (An item with the same key has already been added.)

var args = new Arguments(new object[] {passNow, local, world});

If you debug your code you'll see that there are two items in the dictionary only.

var args = new Arguments {{typeof (IPassNow), passNow}};
args.InsertTypedCollection(new object[] {local, world});

In order to make it work arguments can be passed as anonimus type.

container.Resolve<Event>(new {passNow = passNow, local = local, world = world});

There is a test that proves it

[Fact]
public void ArgumentsAsAnonimusType()
{
    // Arrange.
    var container = new WindsorContainer();
    container.Register(Component.For<Event>().ImplementedBy<Event>());
    container.Register(Component.For<IRegisteredType>().ImplementedBy<RegisteredType>());
    var passNow = new PassNow();
    var local = DateTimeOffset.Now;
    var world = DateTimeOffset.UtcNow;
    local.Should().NotBe(world);

    // Act.
    var result = container.Resolve<Event>(new { passNow = passNow, local = local, world = world });

    // Assert.
    result.Should().BeOfType<Event>();
    result.RegisteredType.Should().Be(container.Resolve<IRegisteredType>());
    result.PassNow.Should().Be(passNow);
    result.Local.Should().Be(local);
    result.World.Should().Be(world);
}
Ilya Palkin
  • 14,687
  • 2
  • 23
  • 36
  • this will not work unfortunately as I am passing in the concrete type PassNow whereas the constructor is expecting the interface IPassNow, which needs to therefore be explicitly specified i.e. see the answer to this question http://stackoverflow.com/questions/14549577/failure-to-pass-generic-arguments-with-castle-windsor – Cel Mar 09 '14 at 10:08
  • sorry, your example does work, because you are specifying literal variable names, but I forgot to mention that I cannot rely on literal variable names because that does not work with obfuscation :( so i cannot do new {passNow = passNow etc because after obfuscation the constructor parameter will not be called passNow any more.. – Cel Mar 09 '14 at 15:51
1

How does your factory looks like ? Did you try TypedFactoryFacility ?

For example, in my project I have a consturctor like this:

public SomeConcrete(ILog log, IDataBase dataBase, int param1, int param2, int param3, bool somebool)

and my factory is:

public interface IFactory
{
    SomeConcrete GetSomeConcrete(int param1, int param2, int param3, bool somebool);
}

and the resolving by variable names works fine.

If you don't wont to use typed factory, you can try:

container.Resolve<ISomeConcrete>(new {param1 = 1, param2 = 2, param3 = 3, somebool = false});

More info here Typed Factory Facility

And maybe this will be usefull too.

Community
  • 1
  • 1
shudima
  • 460
  • 1
  • 5
  • 9
  • Your example is working because you do not have any interface parameters at resolution-time i.e. you do not have the equivalent of IPassNow or GetSomeConcrete(ILog log, int param1, int param2 ..) - again, please see http://stackoverflow.com/questions/14549577/failure-to-pass-generic-arguments-with-castle-windsor – Cel Mar 09 '14 at 13:20
  • I see your point now. What I did to handle this is to resolve my reference prior my concrete resolution. Lets say I want to get ILog during the resolution, So it looks like: ILog log = logFactory.GetLog(); Concrete concrete = concreteFactory.GetConcrete(log, param1, param2, param3, somebool); Hope it helps – shudima Mar 09 '14 at 13:42
  • this wont work, try it, because SomeConcrete(ILog log ...) is expecting ILog when you are passing in GetConcrete(log, ..) (which Castle Windsor sees as Log (and not as ILog) – Cel Mar 09 '14 at 14:00
  • If you don't register ILog anywhere else, it should work. works for me. Sorry I couldnt help. – shudima Mar 09 '14 at 14:40
  • sorry, your example does work, because you are specifying literal variable names, but I forgot to mention that I cannot rely on literal variable names because that does not work with obfuscation :( so i cannot do new {param1 = 1 etc because after obfuscation the constructor parameter will not be called param1 any more.. – Cel Mar 09 '14 at 15:50
  • 1
    In that case, the only thing that I can think about is to create class named EventDate (or something) that will include 2 properties, local and world and pass it to the constructor. that way your constructor will have only one parameter. – shudima Mar 09 '14 at 16:22
0

Thought I found a solution:

public Event CreateEvent(IPassNow passNow, DateTimeOffset local, DateTimeOffset world)
{
    var args = new Arguments(new { local, world });
    args.Insert(typeof(IPassNow), passNow);
    return _container.Resolve<Event>(args);
}

But this does not work with obfuscation either, turns out that new Arguments(new { local, world }); uses parameter literal name as well rather than matching by order or something .. so this is not really an answer ..

Cel
  • 6,467
  • 8
  • 75
  • 110