The WPF application I am working on will have numerous add-ins to extend its functionality. Each add-in will consist of one or several assemblies (usually a "main" add-in assembly with separate assemblies for presentation-layer components and views) but will be treated by the application as a single Prism module. Prism will be used to discover and load the add-ins, with MEF used to ensure that the Prism module can access dependencies in the add-in's related assemblies. The Prism module's Initialize method will be responsible for, amongst other things, configuring the IoC container (in this case, Unity). In a deployed scenario, this will all be loaded and managed with a MefBootstrapper at startup.
My problem occurs when trying to unit test an add-in. To keep the add-in code semi-isolated from the main application, each add-in will also have its own unit test assemblies. One of these test assemblies will be responsible for checking registration of services with the IoC container. In the test scenario, I don't want to use a bootstrapper to load the Prism modules as they have dependencies that I don't want to introduce to my test assemblies. I have therefore written my test fixture base class such that it creates its own MefModuleManager to load the module to be tested.
ResolutionTestBase.cs
public abstract class ResolutionTestBase
{
[ClassInitialize]
public static void TestFixtureInitialise(TestContext context)
{
// Create the main resolution container.
var container = new UnityContainer();
// Install the service locator.
var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);
}
// Here go some helper methods for performing resolution tests.
protected IUnityContainer Container
{
get { return ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer; }
}
}
AddInResolutionTestBase.cs
public abstract class AddInResolutionTestBase:ResolutionTestBase
{
static AddInResolutionTestBase()
{
Logger = new EmptyLogger();
}
[TestInitialize]
public virtual void TestInitialise()
{
// Create MEF catalog.
var aggregateCatalog = new AggregateCatalog();
foreach (var testAssembly in TestAssemblies)
{
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(testAssembly));
}
// Load module manager.
var container = new CompositionContainer(aggregateCatalog);
var serviceLocator = new MefServiceLocatorAdapter(container);
var parts = new DownloadedPartCatalogCollection();
var moduleInitialiser = new MefModuleInitializer(serviceLocator, Logger, parts, aggregateCatalog);
var moduleManager = new MefModuleManager(moduleInitialiser, ModuleCatalog, Logger);
moduleManager.ModuleTypeLoaders = new[] { new MefFileModuleTypeLoader() };
moduleManager.Run();
}
protected static ILoggerFacade Logger { get; private set; }
protected abstract IModuleCatalog ModuleCatalog { get; }
protected abstract IEnumerable<Assembly> TestAssemblies { get; }
}
For the add-ins, they have a stand-alone class in their main assembly to implement the requirements of a Prism module, and Unity extensions to configure the container.
Module.cs
[ModuleExport("AddInModule", typeof(Module), InitializationMode = InitializationMode.OnDemand)]
public class Module : IModule
{
private readonly IEnumerable<IUnityContainerExtensionConfigurator> _extensions;
[ImportingConstructor]
public Module([ImportMany]IEnumerable<IUnityContainerExtensionConfigurator> extensions)
{
_extensions = extensions;
}
public void Initialize()
{
// Load the dependency injection container.
var container = ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer;
if (container != null)
{
foreach (var extension in _extensions)
{
container.AddExtension((UnityContainerExtension) extension);
}
}
}
}
ContainerInstallerExtension.cs (in the add-in's main assembly)
[Export(typeof(IUnityContainerExtensionConfigurator))]
public class ContainerInstallerExtension : UnityContainerExtension
{
protected override void Initialize()
{
// perform container configuration here.
}
}
PresentationInstallerExtension.cs (in the add-in's presentation assembly)
[Export(typeof(IUnityContainerExtensionConfigurator))]
public class PresentationInstallerExtension:UnityContainerExtension
{
protected override void Initialize()
{
// perform container configuration here.
}
}
AddInResolutionTest.cs (in the add-in's IoC test assembly)
[TestClass]
public class AddInResolutionTest : AddInResolutionTestBase
{
private IEnumerable<Assembly> _testAssemblies;
private IModuleCatalog DoGetModuleCatalog()
{
var moduleInfo = new ModuleInfo("AddInModule", typeof (Module).AssemblyQualifiedName)
{
InitializationMode = InitializationMode.WhenAvailable,
Ref = typeof (Module).Assembly.CodeBase
};
return new ModuleCatalog(new[] {moduleInfo});
}
protected override IModuleCatalog ModuleCatalog
{
get { return DoGetModuleCatalog(); }
}
protected override IEnumerable<Assembly> TestAssemblies
{
get { return _testAssemblies ?? (_testAssemblies = new[] { typeof(ContainerInstallerExtension).Assembly, typeof(PresentationInstallerExtension).Assembly }); }
}
[TestMethod]
public void ResolveSomeService()
{
// perform resolution test here.
}
}
Of note with the resolution test fixture, the "test assemblies" are linked to the IoC test assembly with project references and referred to directly by type (rather than using a directory-scan catalog) so I could avoid having to use a post-build event to copy assemblies to a common folder for testing.
When I run the unit tests (as-is), I get an exception indicating the module manager failed to load the Prism module:
Initialization method AddInResolutionTest.TestInitialise threw exception. Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException: Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException: Failed to load type for module AddInModule.
If this error occurred when using MEF in a Silverlight application, please ensure that the CopyLocal property of the reference to the MefExtensions assembly is set to true in the main application/shell and false in all other assemblies.
Error was: Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference not set to an instance of an object..
at Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo)
--- End of inner exception stack trace ---
at Microsoft.Practices.Prism.Modularity.ModuleManager.HandleModuleTypeLoadingError(ModuleInfo moduleInfo, Exception exception)
at Microsoft.Practices.Prism.Modularity.ModuleManager.IModuleTypeLoader_LoadModuleCompleted(Object sender, LoadModuleCompletedEventArgs e)
at Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e)
at Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(ModuleInfo moduleInfo, Exception error)
at Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo)
at Microsoft.Practices.Prism.Modularity.ModuleManager.BeginRetrievingModule(ModuleInfo moduleInfo)
at Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModuleTypes(IEnumerable`1 moduleInfos)
at Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModulesWhenAvailable()
at Microsoft.Practices.Prism.Modularity.ModuleManager.Run()
at AddInResolutionTestBase.TestInitialise() in AddInResolutionTestBase.cs: line xx
At the point of calling moduleManager.Run() nothing in my code is null so it is not clear to me what the "real" problem is.
I have tried various changes to resolve the problem including:
calling moduleManager.LoadModule() instead of moduleManager.Run() in AddInResolutionTestBase.cs
manipulating the State of the ModuleInfo created in AddInResolutionTest to bypass the problem in the module manager
Any other changes I've made have resulted in different errors, but still indicate a problem with the module manager trying to load the Prism module.
Is there some additional step required to correctly configure the module manager to be able to load modules in this way, bearing in mind that some of the usual overhead (such as the logger) is not required for the unit tests?
With the aid of a de-compiler, I was able to figure out the "missing pieces" and make some changes to ensure that all the required components are registered/installed for the module manager to be able to initialise the module(s) for testing. For anyone who is interested:
public abstract class AddInResolutionTestBase:ResolutionTestBase
{
private CompositionContainer _container;
private IModuleCatalog _moduleCatalog;
private IEnumerable<object> _testEntities;
private IEnumerable<ModuleInfo> _testModuleInformation;
static AddInResolutionTestBase()
{
Logger = new EmptyLogger();
}
[TestInitialize]
public virtual void TestInitialise()
{
// Create MEF catalog.
AggregateCatalog = CreateAggregateCatalog();
ConfigureAggregateCatalog();
AggregateCatalog = DefaultPrismServiceRegistrar.RegisterRequiredPrismServicesIfMissing(AggregateCatalog);
ConfigureContainer();
// Initialise modules to be tested.
CompositionContainer.GetExportedValue<IModuleManager>().Run();
}
#region Protected Methods
protected virtual void ConfigureAggregateCatalog()
{
var testAssemblies = TestEntities.OfType<Assembly>();
foreach (var testAssembly in testAssemblies)
{
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(testAssembly));
}
if (TestEntities.Any(entity => entity is System.Type))
{
var catalog = new TypeCatalog(TestEntities.OfType<System.Type>());
AggregateCatalog.Catalogs.Add(catalog);
}
}
protected virtual void ConfigureContainer()
{
CompositionContainer.ComposeExportedValue<ILoggerFacade>(Logger);
CompositionContainer.ComposeExportedValue<IModuleCatalog>(ModuleCatalog);
CompositionContainer.ComposeExportedValue<IServiceLocator>(new MefServiceLocatorAdapter(CompositionContainer));
CompositionContainer.ComposeExportedValue<AggregateCatalog>(AggregateCatalog);
}
protected virtual AggregateCatalog CreateAggregateCatalog()
{
return new AggregateCatalog();
}
protected virtual CompositionContainer CreateContainer()
{
return new CompositionContainer(AggregateCatalog);
}
protected virtual IModuleCatalog CreateModuleCatalog()
{
return new ModuleCatalog(TestModuleInformation);
}
protected abstract IEnumerable<object> GetTestEntities();
protected abstract IEnumerable<ModuleInfo> GetTestModuleInformation();
#endregion
#region Protected Properties
protected AggregateCatalog AggregateCatalog { get; set; }
protected CompositionContainer CompositionContainer
{
get { return _container ?? (_container = CreateContainer()); }
}
protected static ILoggerFacade Logger { get; private set; }
protected IModuleCatalog ModuleCatalog
{
get { return _moduleCatalog ?? (_moduleCatalog = CreateModuleCatalog()); }
}
protected IEnumerable<object> TestEntities
{
get { return _testEntities ?? (_testEntities = GetTestEntities()); }
}
protected IEnumerable<ModuleInfo> TestModuleInformation
{
get { return _testModuleInformation ?? (_testModuleInformation = GetTestModuleInformation()); }
}
#endregion
}
This test base class now mimics to some extent what normally goes on in the boostrapper when the application normally starts. The (resolution) tests in each of the add-ins now only need to provide a list of the (exported) container extensions and module information for the Prism module that represents the add-in (in addition to the actual resolution tests!)
Related
I'm working on integrating a legacy database with Asp.Net Zero. I created the model classes using EntityFramework Reverse POCO Generator in a separate Models class library project. I also reversed engineered the DbContext into a separate Data class library project. I would like to use the Data Onion framework for my repositories and unit of work. When I use the recommended IOC container Autofaq my Test Winform application works correctly.
However, the Web Project utilizes Castle.Windsor. I'm uncertain on how to do the wire-up.
I'm creating a new container called ClientDesktopContainer:
internal class ClientDesktopContainer : WindsorContainer
{
public ClientDesktopContainer()
{
RegisterComponents();
}
private void RegisterComponents()
{
var connectionstring = ConfigurationManager.ConnectionStrings["MyDbContext"].ConnectionString;
// Data Onion
Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(new DbContextConfig(connectionstring, typeof(MyDbContext), new MigrateToLatestVersion(new Seeder())));
Component.For<IDbContextScope>().ImplementedBy<DbContextScope>();
Component.For<IDbContextScopeFactory>().ImplementedBy<DbContextScopeFactory>();
Component.For<IAmbientDbContextLocator>().ImplementedBy<AmbientDbContextLocator>();
Component.For<IDbContextReadOnlyScope>().ImplementedBy<DbContextReadOnlyScope>();
// Data Onion Unit of Work
Component.For<IRepositoryLocator>().ImplementedBy<RepositoryLocator>();
// Component.For<IRepositoryResolver>().ImplementedBy<CastleWindsorRepositoryResolver>();
Component.For<IUnitOfWorkFactory>().ImplementedBy<UnitOfWorkFactory>();
Component.For<IUnitOfWork>().ImplementedBy<UnitOfWork>();
Component.For<IReadOnlyUnitOfWork>().ImplementedBy<IReadOnlyUnitOfWork>();
// Custom
Component.For<IRepository<Enrollment>>()
.ImplementedBy<BaseRepository<Enrollment, MyDbContext>>();
}
My application invocation code is Program:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
IoC.Initialize(new ClientDesktopContainer());
var dbContextScopeFactor = IoC.Resolve<IDbContextScopeFactory>();
using (var dbReadOnly = dbContextScopeFactor.CreateReadOnly())
{
var context = dbReadOnly.DbContexts.Get<MyDbContext>();
var individuals = context.Enrollments.ToList();
foreach (var individual in individuals)
{
// do stuff
}
}
Application.Run(new ViewMain());
}
}
I created a static IOC:
public static class IoC
{
private static IWindsorContainer _container;
public static void Initialize(IWindsorContainer container)
{
_container = container;
}
public static T Resolve<T>()
{
try
{
return _container.Resolve<T>();
}
catch
{
throw;
}
}
}
The Data Onion documentation mentions registering a custom Resolver for IRepositoryResolver.
I created a CastleWindsorRepositoryResolver:
public class CastleWindsorRepositoryResolver : IRepositoryResolver
{
public IRepository<TEntity> Resolve<TEntity>() where TEntity : class
{
// TODO: Resolve wire-up goes here
throw new System.NotImplementedException();
}
}
I'm receiving a ComponentNotFoundExpection:
Updated to fix constructor parameter for DbContextFactory (to RegisterComponents method):
var dbContextConfig = new DbContextConfig[]
{
new DbContextConfig(
connectionString,
typeof(MyDbContext),
new MigrateToLatestVersion(new Seeder())
)
};
// Data Onion
Register(Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(Dependency.OnValue<DbContextConfig[]>(dbContextConfig)));
Add call to Register in:
internal class ClientDesktopContainer : WindsorContainer
{
public ClientDesktopContainer()
{
RegisterComponents();
}
private void RegisterComponents()
{
var connectionstring = ConfigurationManager.ConnectionStrings["MyDbContext"].ConnectionString;
/* HERE CALL TO REGISTER: */
this.Register(
// Data Onion
Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(new DbContextConfig(connectionstring, typeof(MyDbContext), new MigrateToLatestVersion(new Seeder()))),
Component.For<IDbContextScope>().ImplementedBy<DbContextScope>(),
Component.For<IDbContextScopeFactory>().ImplementedBy<DbContextScopeFactory>(),
Component.For<IAmbientDbContextLocator>().ImplementedBy<AmbientDbContextLocator>(),
Component.For<IDbContextReadOnlyScope>().ImplementedBy<DbContextReadOnlyScope>(),
// Data Onion Unit of Work
Component.For<IRepositoryLocator>().ImplementedBy<RepositoryLocator>(),
// Component.For<IRepositoryResolver>().ImplementedBy<CastleWindsorRepositoryResolver>(),
Component.For<IUnitOfWorkFactory>().ImplementedBy<UnitOfWorkFactory>(),
Component.For<IUnitOfWork>().ImplementedBy<UnitOfWork>(),
Component.For<IReadOnlyUnitOfWork>().ImplementedBy<IReadOnlyUnitOfWork>(),
// Custom
Component.For<IRepository<Enrollment>>()
.ImplementedBy<BaseRepository<Enrollment, MyDbContext>>() );
}
Without Register you are just creating registration object without actually putting types in container. Another thing that may help, by default Castle will register components as singletons add LifestyleTranscient or PerWebRequest to your UnitOfWork registrations.
I've implemented a very small plugin system in C# with MEF. But my plugins won't be loaded. In the Aggregate-Catalog I can see my plugin listed. But, after I'll compose these parts, there isn't my plugin in the plugin list, what I'm doing wrong?
Here's a snippet of my code:
Plugin-Loader:
[ImportMany(typeof(IFetchService))]
private IFetchService[] _pluginList;
private AggregateCatalog _pluginCatalog;
private const string pluginPathKey = "PluginPath";
...
public PluginManager(ApplicationContext context)
{
var dirCatalog = new DirectoryCatalog(ConfigurationManager.AppSettings[pluginPathKey]);
//Here's my plugin listed...
_pluginCatalog = new AggregateCatalog(dirCatalog);
var compositionContainer = new CompositionContainer(_pluginCatalog);
compositionContainer.ComposeParts(this);
}
...
And here, the plugin itself:
[Export(typeof(IFetchService))]
public class MySamplePlugin : IFetchService
{
public MySamplePlugin()
{
Console.WriteLine("Plugin entered");
}
...
}
You're doing this wrong. The ImportMany attribute of the _pluginList field doesn't make any sense, since the the plugin manager instance will be created by you, not by the DI container.
You have to create another class that will import all your plugins.
[Export]
class SomeClass
{
readonly IFetchService[] pluginList;
[ImportingConstructor]
public SomeClass([ImportMany(typeof(IFetchService))]IFetchService[] pluginList)
{
this.pluginList = pluginList;
}
}
Now, you can let the DI container compose an instance of this SomeClass for you. You will see that its pluginList field contains your plugin reference.
I have a Autofac module as below
public class ServiceInjector:Module
{
protected override void Load(ContainerBuilder builder)
{
// many registrations and type looking up here
...
// One of the registration, say t which is found
// in above looking, is a resource consuming type
builder.RegisterType(t).As<ITimeConsume>();
// ...
}
}
And this module is used in a ServiceClass:
public class ServiceClass
{
static IContainer _ioc;
public ServiceClass()
{
var builder = new ContainerBuilder();
builder.RegisterModule<ServiceInjector>();
_ioc = builder.Build();
}
public void InvokeService()
{
using(var scope = _ioc.BeginLifetimeScope())
{
ITimeConsume obj = scope.Resolve<ITimeConsume>(...);
var result = obj.DoTimeConsumingJob(...);
// do something about result here ...
}
}
}
My questions is: how do I test ServiceClass by mocking (Moq) ITimeConsume class ? Here I try to write a test below:
public void Test()
{
Mock<ITimeConsume> moc = GetMockObj(...);
// How can I inject moc.Object into ServiceInjector module,
// so that ServiceClass can use this mock object ?
}
If this is not possible for the way, what's a better design for mocking the time consuming class which can also be injected?
**
Update:
**
Thanks #dubs and #OldFox hints. I think the key is that the Autofac injector should be initialized externally instead of internal controlled. So I leverage 'On Fly' building capability of Autofac.ILifetimeScope and design ServiceClass constructor with a LifeTime scope parameter. With this design I can on-flying registering any service in the unit test as below example:
using(var scope = Ioc.BeginLifetimeScope(
builder => builder.RegisterInstance(mockObject).As<ITimeConsume>())
In the current design you cannot inject your mock object.
The simplest solution with the least changes is to add an Internal Cto'r to ServiceClass:
internal ServiceClass(IContainer ioc)
{
_ioc = ioc;
}
Then use the attributte InternalsVisibleTo to enable the using of the C`tor in your test class.
In the arrange/setup/testInit phase initialize your class under test with the container which contains the mock object:
[SetUp]
public void TestInit()
{
Mock<ITimeConsume> moc = GetMockObj(...);
builder.RegisterInstance(moc).As<ITimeConsume>();
...
...
_target = new ServiceClass(builder.Build());
}
Personally I have multiple container instances. One for each endpoint.
Test project
public class AutofacLoader
{
public static void Configure()
{
var builder = new ContainerBuilder();
builder.RegisterModule<ServiceProject.ServiceInjector>();
builder.RegisterModule<LocalTestProject.AutofacModule>();
Container = builder.Build();
}
public static IContainer Container { get; set; }
}
The local test project autofac module is then free to override the service project module with specific registrations.
If more than one component exposes the same service, Autofac will use the last registered component as the default provider of that service: http://autofac.readthedocs.org/en/latest/register/registration.html#default-registrations
Test class
public void Test()
{
AutofacLoader.Configure();
var x = AutofacLoader.Container.Resolve<ITimeConsume>();
}
I'm using Caliburn.Micro 2.0.2 with the default MEF (Managed Extensibility Framework) IoC container configuration.
I am requesting a collection of ViewModels that implement a certain interface by using IoC.GetAll<ISupportFeatureX> (Yes, I'm eventually removing the ServiceLocator anti-pattern).
All aforementioned ViewModels are decorated with the [Export(typeof(ISupportFeatureX))] attribute.
Everything works as expected until the default view is loaded in OnStartup(). For some reason, GetAllInstances() is called instead of GetInstance() and I get an exception.
"Could not locate any instances of contract <Namespace>.Views.ShellView."
My CaliburnBootstrapper is as follows:
public class CaliburnBootstrapper : BootstrapperBase
{
CompositionContainer container;
public CaliburnBootstrapper() { this.Initialize(); }
void ConfigureIocContainer()
{
try
{
var catalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x))
.OfType<ComposablePartCatalog>());
this.container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
var eventAggregator = new EventAggregator();
var windowManager = new WindowManager();
batch.AddExportedValue<IWindowManager>(windowManager);
batch.AddExportedValue<IEventAggregator>(eventAggregator);
batch.AddExportedValue(this.container);
batch.AddExportedValue(catalog);
this.container.Compose(batch);
}
catch (Exception e)
{
this.logger.Error(e);
throw;
}
}
}
protected override void BuildUp(object instance)
{
this.container.SatisfyImportsOnce(instance);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
var contract = AttributedModelServices.GetContractName(serviceType);
var exports = this.container.GetExportedValues<object>(contract);
if (exports.Any()) return exports;
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override object GetInstance(Type serviceType, string key)
{
var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = this.container.GetExportedValues<object>(contract);
if (exports.Any()) return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
this.DisplayRootViewFor<IShellViewModel>();
}
My Views and ViewModels are in separate projects, hence the SelectAssemblies override.
protected override IEnumerable<Assembly> SelectAssemblies()
{
var assemblies = new List<Assembly>();
assemblies.Add(typeof (IShellViewModel).Assembly);
assemblies.Add(typeof (ShellView).Assembly);
return assemblies;
}
The app worked fine with this Caliburn.Micro setup until I override GetAllInstances(). Looking at the Caliburn.Micro source code, I can't see any calls to GetAllInstances() in the DisplayRootViewFor() call chain.
Any explanation as to why GetInstance() resolves ShellView but the same code in GetAllInstances() does not?
The default behaviour of Caliburn.Micro (CM) is to instantiate our views for you. Once you override "GetAllInstances", CM no longer creates the view instances for you, but instead, looks for objects from the container.
Since the Views are not registered with the container (in this case, using MEF [Export] attribute), the GetAllInstances call throws an exception when the requested View is not found in the container.
This was a simple issue of not understanding the internals of Caliburn.Micro and MEF as the IoC container.
I am trying to write a small module system with MEF, so I have a base module like so:
public abstract class Module : IModule
{
[Import]
private IShell _shell;
protected IShell Shell
{
get { return _shell; }
}
}
And a few modules like so:
[Export(typeof(IModule))]
public class TodoListModule : Module
{
}
As you see, the base module is dependent on an IShell. I have 1 implementation of that, like so:
[Export(typeof(IShell))]
public class ShellViewModel : IShell
{
[ImportMany]
private IEnumerable<IModule> _modules;
.... and later...
private void InitializeModules()
{
foreach (var module in _modules)
{
// init modules.
}
}
}
So the shell is responsible for loading modules.
The problem is when I have more than 1 module implementation, I get this exception:
GetExportedValue cannot be called before prerequisite import '...ShellViewModel..ctor' has been set.
So, the cause is the fact that we have 2 implementations that are getting an IShell thru the base class. Why is this happening?
This is my container config:
// Add all assemblies to AssemblySource (using a temporary DirectoryCatalog).
var directoryCatalog = new DirectoryCatalog(#"./");
AssemblySource.Instance.AddRange(
directoryCatalog.Parts
.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
.Where(assembly => !AssemblySource.Instance.Contains(assembly)));
// Prioritise the executable assembly. This allows the client project to override exports, including IShell.
// The client project can override SelectAssemblies to choose which assemblies are prioritised.
var priorityAssemblies = SelectAssemblies().ToList();
var priorityCatalog = new AggregateCatalog(priorityAssemblies.Select(x => new AssemblyCatalog(x)));
var priorityProvider = new CatalogExportProvider(priorityCatalog);
// Now get all other assemblies (excluding the priority assemblies).
var mainCatalog = new AggregateCatalog(
AssemblySource.Instance
.Where(assembly => !priorityAssemblies.Contains(assembly))
.Select(x => new AssemblyCatalog(x)));
var mainProvider = new CatalogExportProvider(mainCatalog);
container = new CompositionContainer(priorityProvider, mainProvider);
priorityProvider.SourceProvider = container;
mainProvider.SourceProvider = container;
var batch = new CompositionBatch();
BindServices(batch);
batch.AddExportedValue(mainCatalog);
container.Compose(batch);
This also happens when any other class is importing IShell