开发者

Caliburn Micro: plugin-related questions for MEF-based scenario

开发者 https://www.devze.com 2023-03-10 11:05 出处:网络
I have a scenario with a WPF application hosting some views (user controls) with their viewmodels, found as MEF exported parts in its plugin folder. The application loads its data together with a conf

I have a scenario with a WPF application hosting some views (user controls) with their viewmodels, found as MEF exported parts in its plugin folder. The application loads its data together with a configuration file, which also tells which parts should be imported among the available ones.

First question is related to the MEF bootstrapper: how can I customize it in order to let it know about the plugins folder? I know of its SelectAssemblies override, but it requires assemblies, while my typical approach would be a MEF directory catalog. I would not want to use methods like Assembly.LoadFrom for each DLL found in the directory: MEF is there for this purpose (lifetime managing and the like). So how can I do something similar to a MEF AggregateCatalog built of DirectoryCatalog's in the bootstrapper?

Second question: once I have the list of VM's I need, I want to instantiate them. Some of them require the injection of CM services like IEventAggregator or IWindowManager, so that they have a corresponding importing constructor and I thus require CM to instantiate them for me: but I need to do this programmatically, so I can't just use an Import attribute on a property or an importing constructor.

The same is true for views: once I have got my VM's, I need CM to create them and set the corresponding VM as their data context; but I cannot use a window manager because I just want to get them, and then programmatically add them (they are user controls) to a tab control, which is composed differently according to the data configuratio开发者_JAVA技巧n.

I'm using MEF because the application is plugin-centric, and so I can stick with its limitations when used as a IoC. But I'd like to leverage CM for instantiating my views and viewmodels (all contained in several plugin DLL's) and bind them correctly. Could anyone give some hints or point to samples or documentation about this?

Second update, as promised :):

I essentially follow these steps:

  1. instantiate (using MEF) the required VM's according to some application logic. All the VM's are MEF exports, hosted in several plugins in a plugins folder.

  2. for each VM, call:

static private object LocateViewFor(object viewmodel)
{
  UIElement view = ViewLocator.LocateForModel(viewmodel, null, null);
    ViewModelBinder.Bind(viewmodel, view, null);
  return view;
}

This should istantiate my views via CM, which will also satisfy their imports and bind each view to its VM. The "standard" MefBootstrapper, modified for WPF, is used (see e.g. here). Anyway, this does not work and returns null.

I must tell the bootstrapper where it can find my MEF exports. They are in the plugins folder, and if I weren't using CM I'd use a MEF DirectoryCatalog to inspect its content. The typical extension point for bootstrappers in CM is a SelectAssemblies override, which requires me to return a list of Assembly objects. Loading all the assemblies from a folder isn't an option. Following a suggestion in the above quoted page, I could do something like adding a method like this in my bootstrapper:

private IEnumerable GetDirectoryCatalogs()
{
 return new ComposablePartCatalog[]
 {
 new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory)
 // TODO other plugins folders...
  };
}

And modify its configuration code like:

_container = new CompositionContainer(
  new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType()
  .Union(GetDirectoryCatalogs())));

This effectively seems to load the required assemblies as for MEF, but it is not enough to "register" them into CM AssemblySource.Instance: infact, I can retrieve the VM's via MEF, but when I ask CM to get the corresponding view (either with the above method, or with a call to IWindowManager.ShowDialog(myviewmodel, null)) I get null from the method, or (for the second sample) the "placeholder" textbox saying that a view for my VM could not be located.

This seems related to the fact that the view, like the viewmodel, is hosted into a different assembly, my MEF plugin. I ensured that both the view and the viewmodel share the same namespace, and follow the naming convention of type SomeNamespace.SampleViewModel - SomeNamespace.SampleView; also, both the view and the viewmodel are exported by decorating them with [Export], and derive from common interfaces. Nonetheless, I cannot get CM to work as expected when dealing with external assemblies. MEF work fine, with all its imports and exports, but as soon as CM enters the equation it cannot do its "magic" and locate a view from the VM.

Any hint?


I've never done anything with Caliburn Micro before, but does it use the CompositionInitializer component to satisfy imports? If so, you can use the CompositionHost to manually initialise the container used to satisfy imports, e.g.:

var catalog = new AggregateCatalog(
    new DirectoryCatalog("bin"),
    new DirectoryCatalog("Plugins"));

CompositionHost.Initialise(catalog);

Any calls to CompositionInitialiser.SatisfyImports(...) will use a container constructed from the catalogs you created.

If you are looking to be able to create instances of exports programmatically, you will need reference to the actual CompositionContainer itself. But to do this, we need another alternative way of initialising composition. You could create a container and hold a reference to it, e.g.:

var catalog = new AggregateCatalog(
    new DirectoryCatalog("bin"),
    new DirectoryCatalog("Plugins"));
var container = new CompositionContainer(catalog);
CompositionHost.Initialise(container);

Container = container;

...where Container is a static reference to your CompositionContainer instance. We've changed the call to CompositionHost to specificy the exact container that CompositionInitializer will use, so that all still works correctly, but we then have a container that we can use to create specific instances, e.g.:

var viewModel = Container.GetExport<ISomeViewModel>();

Does that help?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号