How do I use StructureMap with generic unclosed ty

2020-02-20 06:52发布

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

2条回答
贪生不怕死
2楼-- · 2020-02-20 07:17

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.

查看更多
\"骚年 ilove
3楼-- · 2020-02-20 07:26

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>>();
查看更多
登录 后发表回答