views:

438

answers:

1

Between various Stack Overflow questions and blog posts there is a pretty reasonable amount of documentation on the topic of open generics and StructureMap. Unfortunately, I must be missing something as my attempts at using scan to perform the configuration along with a class implementation that has a "greedy" constructor have yet work.

I want StructureMap to grab an instance of the below class via references to its implemented interface. ToCsvService exists in an unreferenced assembly called Infrastructure. IToCsvService exists in a referenced assembly called Core. As you can see ToCsvService has a "greedy" constructor.

public class ToCsvService<TSource> : IToCsvService<TSource>
{
    public ToCsvService(ICollection<TSource> collection)
    {
    }
}

I let StructureMap know about ToCsvService via the ConnectImplementationsToTypesClosing method.

ObjectFactory.Initialize(cfg =>
{
    cfg.Scan(scan =>
    {
        scan.Assembly("Infrastructure");
        scan.WithDefaultConventions();

        // even with this call StructureMap cannot use ToCsvService
        // instance of IToCsvService (though wouldn't expect it to)
        scan.ConnectImplementationsToTypesClosing
            (typeof(IToCsvService<>));
    });
});

From the ObjectFactory.WhatDoIHave() method it appears that StructureMap is aware of ToCsvService.

PluginType                            Name                                                                                                            Description                                                                                                                          
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IToCsvService`1 (IToCsvService`1)                                                                                                                                                                                                                                      
Scoped as:  Transient                                                                                                                                                                                                                                                                                  
                                      6202a7ee-89a4-4edd-831d-4867b7dd1a7e                                                                            Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
                                      Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null   Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                   

However when I specify IToCsvService<CustomerViewModel> in a Controller constructor it throws the exception

StructureMap Exception Code: 202 No Default Instance defined for PluginFamily Core.Services.IToCsvService`1[[UI.Models.MachineForm, UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

I imagine that this is because StructureMap has no idea what to hand the "greedy" ToCsvService constructor. Is there someway that I can make StructureMap able to play nice with this constructor? Do I need to switch from a constructor and just set the collection property after instantiation?

The question Structuremap and generic types details somewhat I am trying to do but does not utilize a scan to do so. The answer provided by Joshua Flanagan utilizes the For(x).Use(y) configuration, which might work if I wasn't scanning the assembly because I don't have a reference ToCsvService.

Edit

I wanted to see if using a different mechanism to let StructureMap identify instances of ToCsvService<> would have an effect. It changes what's reported in ObjectFactory.WhatDoIHave() and throws a different exception. Here's an example of using AddAllTypesOf.

ObjectFactory.Initialize(cfg =>
{
    cfg.Scan(scan =>
    {
        scan.Assembly("Infrastructure");
        scan.WithDefaultConventions();

        scan.AddAllTypesOf(typeof(IToCsvService<>));
    });
});

After using the above the dump from ObjectFactory.WhatDoIHave() is

PluginType                           Name                                                                                                                                 Description                                                                                                                                            
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IToCsvService`1 (IToCsvService`1)    Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                        Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                   
Scoped as:  Transient

                                     Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                        Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                   
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IToCsvService`1 (IToCsvService`1)    Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                        Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                   
Scoped as:  Transient

                                     Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null                        Configured Instance of Infrastructure.Services.ToCsvService`1, Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

With this configuration I throw this exception:

StructureMap Exception Code: 202 No Default Instance defined for PluginFamily System.Collections.Generic.ICollection`1[[UI.Models.MachineForm, UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

To me the exception indicates that StructureMap knows it needs an ICollection<MachineForm> to instantiate ToCsvService but does not know where to get it from. Which goes to Jimmy's comment from below about using StructureMap and Constructor setter injection. However, this doesn't seem possible without adding an explicit reference to the Infrastructure assembly.

Somewhat related Stack Overflow questions on StructureMap & Generics

Blogs posts concerning StructureMap & Generics

A: 

Are there any concrete implementations of IToCsvService? Or just the open type ToCsvService?

ConnectImplementationsToTypesClosing is for connecting something like a concrete CustomerViewModelToCsvService to IToCsvService<>. If you want to connect open classes to open interfaces, you'll need:

For(typeof(IToCsvService<>)).Use(typeof(ToCsvService<>));

Here I'm connecting the open interface type to the open class type.

Jimmy Bogard
Which makes good sense, but wouldn't that require a reference to the assembly containing `ToCsvService`?
ahsteele
Ah, just saw that. The assembly gets loaded eventually though, doesn't it? Eventually you have to have that type loaded. At that point, you can use the container's Configure method. Remember, this For.Use just needs instances of Type objects. There are lots of ways to get to those for an assembly loaded dynamically.
Jimmy Bogard
@Jimmy Based on the dump from `ObjectFactory.WhatDoIHave()` I think `ToCsvService` is being loaded. Assuming that `ToCsvService` has been successfully loaded into StructureMap what would I specify in the Configure method to allow it to use the "greedy" constructor to make `IToCsvService<CustomerViewModel>` not throw an exception?
ahsteele
Last time I checked, StructureMap chooses the greediest constructor. See: http://structuremap.net/structuremap/ConstructorAndSetterInjection.htm
Jimmy Bogard
@Jimmy Thank you for continuing to work with me on this. StructureMap definitely chooses the greediest constructor but do I have any mechanism by which to control the injection dynamically? I don't want to Override the Constructor in the Registry DSL (http://structuremap.net/structuremap/ConstructorAndSetterInjection.htm#section3) every time I want to use `ToCsvService`. I realize I might be way to specific in what I am asking StructureMap to pull off. It boils down to can StructureMap be used to scan for a generic unclosed type that has a “greedy” constructor based on the unclosed type?
ahsteele