MEF with dynamically loaded modules

Aug 4, 2010 at 9:40 PM

Hi!

First: Fantastic work with this framework!!

In the post "Customizing The Bootstrapper" there is the following phrase: "Also, if you are dynamically loading modules, you’ll need to make sure they get registered with your IoC container and the AssemblySoure.Instance when they are loaded."

What would be the best pattern for making the MEF AggregateCatalog available for such a scenario? 

In the sample bootstrapper this is well "hidden" from the rest of the application code within the bootsrapper instance.

I am building an IResult implementation for loading catalogs dynamically, but have difficulties seeing how to best accomplish the MEF registering.

Thanks for any input!

 

Coordinator
Aug 4, 2010 at 9:59 PM

Is this Silverlight or WPF?

Aug 4, 2010 at 10:02 PM

Sorry! It is Silverlight.

Aug 5, 2010 at 12:58 PM
Edited Aug 5, 2010 at 1:05 PM

I concluded that there is now way to accomplish this. Therefor you need to override CM's  ViewLocator.LocateForModelType and do it manually (http://caliburnmicro.codeplex.com/Thread/View.aspx?ThreadId=218561).

Also remove the override for GetAllInstances as implemented in the documentation as it causes serious issues with the viewlocator. (http://caliburnmicro.codeplex.com/Thread/View.aspx?ThreadId=219152)

Coordinator
Aug 5, 2010 at 2:59 PM

Regarding the second issue, I believe I fixed that in my latest code commit.

As an alternative to the DeploymentCatalog, you could write your own download mechanism that lets you get hold of the assembly to send to AssemblySource and pass to MEF. I'll investigate further to see if there is a better way by using MEF and the DeploymentCatalog directly. Perhaps there is an event on CompositionContainer or on AggregateCatalog that will tell you when things are being added? Maybe you can inherit from CompositionContainer and override something?

Aug 5, 2010 at 10:39 PM

This is what I now have running:

1) Made "AggregateCatalog" public and static in my bootstrapper.  Otherwise my bootstrapper is more or less a copy of the sample ("MefBootstrapper").

2) Created my loader class as follows:

    public class CatalogLoadResult : IResult
    {
        private static readonly Dictionary<stringDeploymentCatalog> Catalogs = new Dictionary<stringDeploymentCatalog>();

        private readonly string _uri;

        public CatalogLoadResult(string relativeUri)
        {
            _uri = relativeUri;    
        }

        public void Execute(ActionExecutionContext context)
        {
            DeploymentCatalog catalog;
            if (Catalogs.TryGetValue(_uri, out catalog))
                Completed(thisnew ResultCompletionEventArgs());
            else
            {
                catalog = new DeploymentCatalog(_uri);
                catalog.DownloadCompleted += (s, e) =>
                                                 {
                                                     if (e.Error == null)
                                                     {
                                                         Catalogs[_uri] = catalog;
                                                         MefBootstrapper.AggCat.Catalogs.Add(catalog);
                                                         foreach (var assembly in catalog.Parts
                                                             .Select(part =>
                                                                 ReflectionModelServices.GetPartType(part).Value.Assembly)
                                                                 .Where(assembly => !AssemblySource.Instance.Contains(assembly)))
                                                             AssemblySource.Instance.Add(assembly);
                                                     }
                                                     Completed(thisnew ResultCompletionEventArgs{Error = e.Error, WasCancelled = false}); 
                                                 };
                catalog.DownloadAsync();
            }
        }

        public event EventHandler<ResultCompletionEventArgs> Completed;
    }
With this I can now do like this:
	...
	yield return new CatalogResult("TestCatalog.xap");
	...
 
And later in my code:
	...
	var wm = new WindowManager();
	wm.ShowDialog(IoC.Get<IScreen>("TestViewModel"));
	...
where "TestViewModel" and "TestView" are both contained in "TestCatalog.xap"
It seems to be running fine so far... 
Any thoughts on this solution?
Aug 9, 2010 at 1:18 AM
Edited Aug 9, 2010 at 11:46 AM

Hah!

This is what I wanted to do!
I just could not figure out how I can extract the assembly info from MEF. 
You should go post this solution, its good ;)
I'm gonna change mine to work more like that.
Thanks!
W

Aug 9, 2010 at 1:22 AM
Edited Aug 9, 2010 at 11:47 AM

[Edit] 

Coordinator
Aug 9, 2010 at 4:25 AM
@janoveh Would you be interested in having this code added to our "Recipes" section? I think that future developers could benefit from the work you have done. Let me know.
Aug 9, 2010 at 8:00 AM

I would of course be happy to see this in the "Recipes" section!

One thing to be aware though:

"ReflectionModelServices.GetPartType(part)" returns a "System.Lazy<Type>". 

This means that when I use the "Value" property, lazy initialisation will occur. 

I do not have full insight as to what implications this could potentially have, if any.

Maybe someone could provide some analysis of this.

Aug 12, 2010 at 2:06 PM
I'm trying to get this working but I'm not having any luck so far. Is there a chance you could post up more of your code you used to make this work? I have my XAP files out there, but it doesn't seem to find them. I can only get it to find the MEF Exports within the solution.
Aug 12, 2010 at 6:58 PM

Do you know if the other catalogs gets downloaded at all? Have you set breakpoints in the code above?  DeploymentCatalog will only download xap's from the same remote location as the start-up xap was loaded from.  The other xap's must export types (through MEF) since the code above only works if there are composable parts in the xap.

Aug 12, 2010 at 7:30 PM
Edited Aug 12, 2010 at 7:32 PM

I don't believe any catalogs are getting downloaded. I should probably say that I'm not too familiar with C# but I am working in it for the first time now, usually VB.Net The xap file has been placed in the same location as the start-up. I started with the MefBootstrapper example and made the AggregateCatalog public static like you mentioned I then added the CatalogLoadResult class as above. Then, I tried to figure out what you were doing with:

---------------------------------------------------------------------------

With this I can now do like this: ...

yield return new CatalogResult("TestCatalog.xap");

 ... And later in my code:

... var wm = new WindowManager();

wm.ShowDialog(IoC.Get<IScreen>("TestViewModel")); ...

 ----------------------------------------------------------------------------

but I think that is where my limited C# knowledge is preventing me from understanding what you are actually doing. Where/how is the yield return line being called and what actually runs the Execute on the CatalogLoadResult? The Execute prcoedure in the CatalogLoadResult class never runs in my code.

Aug 12, 2010 at 9:04 PM

OK, this is not so much linked to the status of your C# knowledge. My code makes use of a basic feature of the Caliburn.Micro framework, namely "coroutines".

It looks like there is reason to believe there will be a separete section about this subject under "Documentation" above.

A very short code example:

        public IEnumerable<IResult> LoadCatalogs()
        {
            yield return new CatalogLoadResult("TestCatalog1.xap");
            yield return new CatalogLoadResult("TestCatalog2.xap");
            yield return new CatalogLoadResult("TestCatalog3.xap");
            yield return new CatalogLoadResult("TestCatalog4.xap");
        }

        public void ClickMe()
        {
            new SequentialResult(LoadCatalogs()).Execute(null);
            var wm = new WindowManager();
            wm.ShowDialog(IoC.Get<IScreen>("TestViewModel"));
        }

If the "ClickMe" method was now bound to a button the catalogs would get sequentially downloaded and a pop-up would show up with contents from one of them.

I hope this helps... 

Aug 12, 2010 at 9:08 PM
Ah, great. Thank you. That resolves how to call it and I also just figured out that I was dumping my XAP file into the Silverlight Project bin instead of the ClientBin for my website (Whoops!). I'm coming off a VB.Net/WPF/MVVM project into a Silverlight/C#/MEF/Caliburn/MVVM project and my head is spinning sometimes. Thanks for the help.
Aug 13, 2010 at 5:27 PM
Edited Aug 13, 2010 at 5:29 PM

I tried to use the CatalogLoadResult class but it is not keeping the next statement from executing immediately.

Here is how I am calling it

{

...
                string appNamespace = Application.Current.GetType().Namespace;
                string moduleRelativeUri = string.Format("{0}.{1}.xap", appNamespace, message.Module);
                new SequentialResult(LoadWithDependencies(moduleRelativeUri)).Execute(null);
                Activate(message);
...
}
        private IEnumerable<IResult> LoadWithDependencies(string moduleUri)
        {
            yield return new CatalogLoadResult("TestDependency.xap");
            yield return new CatalogLoadResult(moduleUri);
        }

Activate(message) gets executed immediately after the new SequentialResult call and since my Activate(message) call is dependent on the xap's having been downloaded it fails. I put a break on the CatalogLoadResult and I see that the DownloadCompleted event is executed and the xap's are being downloaded. What am I doing wrong?

 

Aug 13, 2010 at 5:50 PM

Just found out there is a completed event from the SequentialResult class so if I do this:

                var s = new SequentialResult(LoadWithDependencies(moduleRelativeUri));
                s.Completed += (se, ev) =>
                                   {
                                       Activate(message);
                                   };
                s.Execute(null);

It works. Is this the way it is supposed to be used? Still trying to understand how coroutines work.

Aug 13, 2010 at 8:06 PM
Edited Aug 13, 2010 at 8:16 PM

The code example above was written in a hurry and did not come from running code.  I am really sorry for that!

Since, in the case of CatalogLoadResult, an async operation is started inside an IResult.Execute method, with the Completed event called by the async operation, the calling thread will not be used for more than kicking the "spin" in action.

The behavior of SequentialResult will still be that the catalogs are downloaded sequentially, but SequentialResult.Execute will return as soon as the downloading of the first catalog has been set in motion.

So, you need to do something like you suggest to get Activate(message) executed only after the last catalog has downloaded.  You could also put Activate(message) after the last yield in the LoadWithDependencies, since control will be returned to that method eventually after a successful download of the last catalog. .

Anyway, this is how I understand it will work.  "Coroutines" is a new subject also for me, I'm afraid.

 

Aug 13, 2010 at 8:11 PM
Edited Aug 13, 2010 at 8:11 PM

No apologies needed. A lot of us are learning as we go. Thanks for the code it got me what I needed.

Coordinator
Aug 13, 2010 at 8:42 PM
Glad you guys got this figured out. Sorry I didn't get time to respond earlier.
Dec 9, 2010 at 5:31 PM
janoveh wrote:

The code example above was written in a hurry and did not come from running code.  I am really sorry for that!

Since, in the case of CatalogLoadResult, an async operation is started inside an IResult.Execute method, with the Completed event called by the async operation, the calling thread will not be used for more than kicking the "spin" in action.

The behavior of SequentialResult will still be that the catalogs are downloaded sequentially, but SequentialResult.Execute will return as soon as the downloading of the first catalog has been set in motion.

So, you need to do something like you suggest to get Activate(message) executed only after the last catalog has downloaded.  You could also put Activate(message) after the last yield in the LoadWithDependencies, since control will be returned to that method eventually after a successful download of the last catalog. .

Anyway, this is how I understand it will work.  "Coroutines" is a new subject also for me, I'm afraid.

 

What's a good way to address this problem?

Dec 10, 2010 at 12:26 AM

You should just put the last yield in the iterator, as  janoveh suggested