How do I use StructureMap with generic unclosed types using Scan with a "greedy" constructor
Asked Answered
P

2

37

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

Prader answered 28/9, 2010 at 21:29 Comment(0)
N
10

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.

Nigrify answered 29/9, 2010 at 0:59 Comment(6)
Which makes good sense, but wouldn't that require a reference to the assembly containing ToCsvService?Prader
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.Nigrify
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?Prader
Last time I checked, StructureMap chooses the greediest constructor. See: structuremap.net/structuremap/ConstructorAndSetterInjection.htmNigrify
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 (structuremap.net/structuremap/…) 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?Prader
You can specify the constructor that SM uses by the [DefaultConstructor] attribute.Lou
N
0

Actually in the current version it should be very simple. All you have to do is provide the argument when you call to get a new instance of the object. To do this you use the "With" method on the objectfactory.

This allows you to use the greedy constructor. However, it also means you have to know that you need the collection in this example. So it is not the optimal method of injecting state.

var newObject = ObjectFactory.With<ICollection<CustomerViewModel>>(SomeCollection)
                .GetInstance<IToCsvService<CustomerViewModel>>();
Northeaster answered 14/3, 2014 at 4:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.