Trying to GetExportedValues before the CompositionContainer is fully constructed?

Topics: Bootstrappers & IoC, Getting Started
Jul 26, 2012 at 1:54 AM

I am pretty new to both Caliburn.Micro and MEF. My application was working fine at first, but when it passed a certain level of complexity (not very great) in terms of Export and Import attributes, this error began to manifest:

protected override object GetInstance(Type serviceType, string key)
{
    string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
    var exports = _container.GetExportedValues<object>(contract);

... exports.Count() subsequently being equal to 0. However, if I debug the code, the contract that the code is looking for (the <namespace>.IShell contract) is clearly present in the CompositionContainer. In fact, if I set a breakpoint on the last line of code above and then just hit F5 to let the code continue, GetExportedValues finds the exported object. It appears there's a timing issue of the sort mentioned in the subject line.

Has anyone faced this and devised a "wait and retry" loop - probably running on another thread but returning its result to the "current" thread, i.e. the one on which GetInstance was called? Or is there a better way to handle this? And how might I gain insight into why this is happening? Given that it is occurring at a very modest level of code complexity compared to what I hope to develop, I may be in for trouble, especially if I don't know what is happening and why.

Thanks!

George

Jul 26, 2012 at 5:35 AM

Further info: I got past the initial "no go" via this awful hack:

if (exports.Count() == 0)
{
    foreach (ComposablePartDefinition cpDef in _container.Catalog.Parts.AsEnumerable())
    {
        foreach (ExportDefinition exDef in cpDef.ExportDefinitions)
        {
            if (exDef.ContractName == contract)
            {
                Type contractType = Type.GetType(cpDef.ToString());
                exports = new List<object> { Activator.CreateInstance(contractType) };
            }
        }
    }
}</object>
... thereby creating a ShellViewModel instance. However, the said instance was lacking the imported instantiations that I asked MEF to create - because I circumvented MEF to create my ShellViewModel instance, which makes sense. What is needed is a way to (force Caliburn.Micro to) wait until MEF is ready to serve up a ShellViewModel instance with all of its imported parts instantiated - to wait and retry and not to just throw an exception and crash because of CM's eagerness (or some ignorant coding error(s) that I may have made, although trying to follow directions and examples).

Jul 26, 2012 at 6:02 AM

On reflection, I am not sure it is a timing problem. I'm writing code now to dump out all of the import and export definitions in the CompositionContainer to see if there is any dependency that can't be satisfied. Will post update on the results.

Jul 26, 2012 at 7:32 AM

Herewith I present a valuable piece of code that may help others to debug this type of problem. It turned out that I was missing some export attributes matching declared import attributes. I wrote the following method to help me figure out what was going on and to regain a sense of knowledge and mastery concerning what was going on with imports and exports in my project. It's called at the end of Bootstrapper.Configure - and does nothing unless DEBUGABOO is defined at the start of the file. I set a breakpoint after the call, since the only point of running the program when this code is activated is to write the dump file. An app.config setting could be used instead of a #define.

        private void Debugaboo(CompositionBatch batch)
        {
#if DEBUGABOO
            using (StreamWriter sw = new StreamWriter("Parts.txt"))
            {
                var numberByName = new Dictionary<string, int>();
                var number = 1;

                sw.WriteLine("Exports");
                foreach (ComposablePartDefinition cpDef in _container.Catalog.Parts.AsEnumerable())
                {
                    if (cpDef.ExportDefinitions.Count() > 0)
                    {
                        sw.WriteLine("{0,4}. {1}", number, cpDef);
                        foreach (ExportDefinition exDef in cpDef.ExportDefinitions)
                        {
                            numberByName.Add(exDef.ContractName, number);

                            sw.WriteLine("        {0}", exDef.ContractName);
                            foreach (string key in exDef.Metadata.Keys)
                            {
                                sw.WriteLine("          {0}={1}", key, exDef.Metadata[key]);
                            }
                        }

                        ++number;
                    }
                }
                foreach (ComposablePart part in batch.PartsToAdd)
                {
                    if (part.ExportDefinitions.Count() > 0)
                    {
                        sw.WriteLine("{0,4}. {1}", number, "batched ComposablePart");
                        foreach (ExportDefinition exDef in part.ExportDefinitions)
                        {
                            numberByName.Add(exDef.ContractName, number);

                            sw.WriteLine("        {0}", exDef.ContractName);
                            foreach (string key in exDef.Metadata.Keys)
                            {
                                sw.WriteLine("          {0}={1}", key, exDef.Metadata[key]);
                            }
                        }

                        ++number;
                    }
                }

                sw.WriteLine();
                sw.WriteLine("Imports");
                foreach (ComposablePartDefinition cpDef in _container.Catalog.Parts.AsEnumerable())
                {
                    if (cpDef.ImportDefinitions.Count() > 0)
                    {
                        sw.WriteLine("  {0}", cpDef);
                        foreach (ImportDefinition imDef in cpDef.ImportDefinitions)
                        {
                            string exportingPartNumberMessage = "missing";
                            int exportingPartNumber;
                            if (numberByName.TryGetValue(imDef.ContractName, out exportingPartNumber))
                            {
                                exportingPartNumberMessage = exportingPartNumber.ToString();
                            }

                            sw.WriteLine("    {0} ({1})", imDef.ContractName, exportingPartNumberMessage);
                            //sw.WriteLine("      Cardinality={0}", imDef.Cardinality);
                            //sw.WriteLine("      Constraint='{0}'", imDef.Constraint);
                            //sw.WriteLine("      IsPrerequisite={0}", imDef.IsPrerequisite);
                            //sw.WriteLine("      IsRecomposable={0}", imDef.IsRecomposable);
                        }
                    }
                }

#if IncludeMetadata
                sw.WriteLine();
                sw.WriteLine("Metadata");
                foreach (ComposablePartDefinition cpDef in _container.Catalog.Parts.AsEnumerable())
                {
                    if (cpDef.Metadata.Count > 0)
                    {
                        sw.WriteLine("  {0}", cpDef);
                        foreach (string key in cpDef.Metadata.Keys)
                        {
                            sw.WriteLine("    {0}={1}", key, cpDef.Metadata[key]);
                        }
                    }
                }
#endif
            }
#endif
        }

I hope the above helps the next person who has similar problems. It got me past the MEF problems pretty quickly. Please try adding the code and running it if you even suspect that it MIGHT be helpful. :)

George