How do I use StructureMap with generic unclosed types using Scan with a "greedy" constructor
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 开发者_JS百科 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 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
- Structuremap and generic types
- StructureMap setter injection in open generic type?
- StructureMap Auto registration for generic types using Scan
Blogs posts concerning StructureMap & Generics
- Advanced StructureMap: connecting implementations to open generic types
- Advanced StructureMap: custom registration conventions for partially closed types
- Open Generic Types in StructureMap
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.
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>>();
精彩评论