views:

280

answers:

1

Using StructureMap, I'm trying to use setter injection on an open generic type.

I have an abstract generic class:

public abstract class Foo<T1, T2> : IMyInterface<T1,T2>
{
   public ISomeDependency Bar { get; set; }
}

I want to use Setter Injection to resolve "Bar" on any inheritors of Foo. I know I can do this using the [SetterDependency] attribute on Bar but I want to avoid decorating my class that way.

I'd thought I could use ForConcreteType in the DSL like so:

ForConcreteType(typeof(Foo<,>)).Configure.Setter().IsTheDefault();

But ForConcreteType only has a generic implementation.

I have tried to do this in configuration as follows:

For(typeof (Foo<,>))
.Use(typeof (Foo<,>)).SetterDependency<ISomeDependency>("Bar").IsAutoFilled();

This compiles but throws a "cannot be plugged into type" runtime exception when it tries to resolve.

Does anyone know how to accomplish setter injection in this case? Thanks!

EDIT:

As requested, here is an elaborated example of what I'm trying to achieve:

[Test]
public void can_resolve_open_generic_type_using_setter_injection()
{
   ObjectFactory.Initialize(x =>
                                {
x.For<ISession>().Use<DatabaseSession>();
// uncomment next line and it resolves:
// x.SetAllProperties(set => set.OfType<ISession>());
x.ForRequestedType<IHandler<OrderReceivedMessage>>()
.TheDefaultIsConcreteType<OrderHandler>();

                                });

   var instance = ObjectFactory.Container.GetInstance<IHandler<OrderReceivedMessage>>();

   instance.ShouldBeOfType<DatabaseTransactionHandler<OrderReceivedMessage>>();
   instance.ShouldBeOfType<OrderHandler>();

   var asTransactionHandler = (DatabaseTransactionHandler)instance;
   Assert.IsNotNull(asTransactionHandler.Session);

}

public interface IHandler<TMessage>
{
    void Handle(TMessage message);
}

public abstract class DatabaseTransactionHandler<TMessage> : IHandler<TMessage>
{

    // need to inject this with the default ISession
    // works when using [SetterDependency] attribute
    public ISession Session { get; set; }

    public abstract void DoHandle(TMessage message);

    public virtual void Handle(TMessage message)
    {
          using (ITransaction transaction = Session.CreateTransaction())
          {
                try
                {
                    DoHandle(message);
                    transaction.Commit();
                }
                catch (Exception handlerException)
                {
                    transaction.Rollback();
                    throw;
                }                   
          }
     }
}


public class OrderHandler : DatabaseTransactionHandler<OrderReceivedMessage>
{
    public override void DoHandle(OrderReceivedMessage message)
    {
       Order order = CreateOrderFromMessage(message);
       Session.Save(order);
    }
}
+1  A: 

I assume you are retrieving Foo by IMyInterface - not by asking for a Foo. So you will want to do For(typeof(IMyInterface<,>)). This code works for me (using the trunk source code):

var container = new Container(x =>
{
    x.For<ISomeDependency>().Use<TheDependency>();
    x.For(typeof (IMyInterface<,>)).Use(typeof (Foo<,>)).SetterDependency<ISomeDependency>("Bar").IsAutoFilled();
});

var instance = (Foo<string, bool>)container.GetInstance(typeof (IMyInterface<string, bool>));
Console.WriteLine(instance.Bar.GetType().Name);

Alternatively, you could set up a convention, so that ANY type retrieved from the container that has a property of type ISomeDependency will get populated:

var container = new Container(x =>
{
    x.For<ISomeDependency>().Use<TheDependency>();
    x.For(typeof (IMyInterface<,>)).Use(typeof (Foo<,>));
    x.SetAllProperties(set => set.OfType<ISomeDependency>());
});

You can also base your conventions on other criteria using x.SetAllProperties(set => set.Matching(...))

Joshua Flanagan
Actually, I am trying to retrieve Foo by asking for Foo. I'm trying to approximate the functionality of ForConcreteType, which passes the same concrete type to both the ForRequestedType and TheDefault.Is methods. This is because Foo<T1, T2> is abstract. I'm actually resolving a concrete IFoo<T1, T2> based on its generic types. If that concrete type happens to inherit from abstract base type Foo<T1, T2> I want its Bar property to be injected with the default ISomeDependency. This is an "open generic type" problem. I'll vote you up for the SetAllProperties workaround. Thanks!
lordinateur
Ok, I guess I shouldn't be making assumptions. Sometimes it gets hard to follow an example, with all of the IFoo and IMyInterface made up name distractions (your comment mentions an IFoo, which your original post did not). Can you post a complete NUnit test that demonstrates your issue? You can use made up classes and interfaces, just make sure you include their complete definition.
Joshua Flanagan
Sorry about all the Foo stuff. I agree it confuses things. I posted an edit to explain what I'm trying to do in more detail. As you can see, the IHandler<TMessage> is the base type, the DatabaseTransactionHandler<TMessage> is an abstract class that has the Session : ISession property. Session is used by inheritors of DatabaseTransactionHandler<TMessage> for accessing a database session. I want this property to be injected without decorating it with the [SetterDependency] attribute or by using a global SetAllProperties method. Thanks for any and all help!
lordinateur