CardinalityMismatchException

Dec 2, 2008 at 1:59 PM
Edited Dec 2, 2008 at 2:07 PM

I'm abusing the metadata functionality a bit to figure out the various ways you can use it and I've stumbled onto an exception I'm not quite sure why it's happening. There's probably a logical explination that someone can provide and in the worst case scenario it's a bug in the framework code.

The exception I'm getting is

No exports were found that match the constraint '((exportDefinition.ContractName = "Experimental.Mef.Bug.IMessageService") && (exportDefinition.Metadata.ContainsKey("Version") && exportDefinition.Metadata.ContainsKey("TypeId")))'.

The thing is, my meta data doesnt have a TypeId member, only a Version. Below is the code which can be used to reproduce the behaviour

namespace Experimental.Mef.Bug
{
       using System;
       using System.Collections.Generic;
       using System.ComponentModel.Composition;
       using System.Linq;
       using System.Reflection;
       using System.Text;

       class Program
       {
              static void Main()
              {
                     Program app =
                            new Program();
                     app.Compose();

                     if (app.Service != null)
                     {
                            app.Service.GetExportedObject();
                     }

                     Console.WriteLine("Done! Press any key to quit.");
                      Console.ReadLine();
              }

              [Import(typeof(IMessageService))]
              public Export<IMessageService, MessageServiceMetaData> Service { get; set; }

              void Compose()
              {
                     var catalog =
                             new AggregatingComposablePartCatalog();
                     catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));
 
                     var container =
                            new CompositionContainer(catalog);
                     container.AddPart(this);
                     container.Compose();

              }

       }

       [ContractType(MetadataViewType = typeof(MessageServiceMetaData))]
       public interface IMessageService
       {
              void Execute();
       }

       [MetadataAttribute]
       class MessageServiceMetaData : Attribute
       {
              public MessageServiceMetaData(string version)
              {
                     this.Version = new Version(version);
              }

              public Version Version { get; set; }
       }

       [Export(typeof(IMessageService))]
       [MessageServiceMetaData("1.0.0.0")]
       public class NullMessageService : IMessageService
       {
              public void Execute()
              {
                     Console.WriteLine("NullMessageService is executing...");
              }
       }
}

Coordinator
Dec 2, 2008 at 7:42 PM
You will need to define a seperate interface to use for your metadata view.  For now MEF is only going to support interfaces for strongly typed metadata.

Daniel
Dec 2, 2008 at 8:13 PM
Yes, and we should that that enforced - seems like we don't, which is a bug. Your 'TypeId' metadata item comes from the TypeId property of Attribute
Thanks
O.
Dec 2, 2008 at 9:12 PM
Oh snap! I didn't even think for a second that I was using an attribute type and not an interface! I knew I had tested that overload of Export before and got it to work and I was stomped that I ended up with an exception this time ;)

It would be nice if one of the following alternatives where possible

1) Have it accept both an interface and attributes decorated with the MetadataAttribute

2) Make it so that it can pull information from a strongly typed interface on an attribute decorated with the MetadataAttribute like so

    public interface IMessageServiceMetaData
    {
        Version Version { get; set; }
    }

    [MetadataAttribute]
    class MessageServiceMetaDataAttribute : Attribute, IMessageServiceMetaData
    {
        public MessageServiceMetaDataAttribute(string version)
        {
            this.Version = new Version(version);
        }

        public Version Version { get; set; }
    }

In the above it would then pick out the members belonging to the IMessageServiceMetaData interface. An alternative could also be to extent MetadataAttribute to have an overload which aided the framework to know which interface (since there's nothing stopping an attribute from implementing multiple) to concider a metadata interface

MetadataAttribute(typeof(IMessageServiceMetaData))

If the attribute allowed multiple instances on the same type then you could expose multiple metadata interfaces.

Would any of the above make any sense?

Dec 2, 2008 at 11:13 PM
I just thought about something else, and please correct me if I'm wrong, but with the above limitation wouldn't you be out of luck to setup a metadata requirement on a contract you didn't own? For example say you want to import a set of UserControls and only load those that have been decorated with a set of metadata you need.

You wouldn't be able to use the strongly-typed interface approach since you do not own the UserControl class so you cannot decorate it with the ContractType attribute and set the metadata dependency using the MetadataType parameter. The only option you have, to send meta data with your parts would be to either use the ExportMetadataAttribute (untyped, suffers from boxing since the value parameter is of the object type, and quite ugly if you need to pass a fairly elaborate piece of meta data) or derive your own attriute and stick the MetadataAttribute on it. Using the latter, you would be unable to tell the framework to only load exported parts of the type UserControl which also conformed to the metadata constraint you wanted, i.e the import below would be impossible

[Import(typeof(UserControl))]
public List<Export<UserControl, IMyMetadata>> Controls { get; set; }

The option (as I see it, with my limited experience of MEF) would be to use a named Export/Import to create a sub-set of the potential UserControl parts, but even that doesn't do the same thing, since it would take any UserControl which was exported using the named contract, leaving you open to the possibility of the metadata being omitted.. which in turn would force you to instead use something like

        [Import("Controls.Pages")]
        private List<Export<ViewPage>> Parts { get; set; }

        public List<Export<ViewPage>> Pages
        {
            get
            {
                var valid =
                    from p in this.Parts
                    where p.Metadata.Any(m => m.Value.GetType().GetCustomAttributes(typeof(MyMetaDataAttribute), true).Length > 0)
                    select p;

                return valid.ToList();
            }
        }

To make sure you only exposed the parts which was tagged with the MyMetaDataAttribute. But even that isn't optimal since it makes you perform a filtering yourself and because the collection used by the Parts property would be contaminated with parts which isn't valid.

So to sum it up.. I definitly think that the Export<T, TMetadata> construct should accept attributes tagged with MeatadataAttribute unless there is another way to put on a metadata constrain on a contract you do not own/controll and be sure you don't run the risk of contaminating your imported parts collection with parts that doesn't conform to the metadata restrictions you may want to impose???

Coordinator
Dec 3, 2008 at 12:36 AM
How the metadata is supplied and how it is consumed are not directly related.  In between them there is always an IDictionary<string, object> which represents the metadata.  So it doesn't matter if you specify the metadata on your export viea the ExportMetadataAttribute or via a custom attribute with the MetadataAttributeAttribute on it.  You can still use an interface on the consumer side.  You can also use the untyped dictionary if you like.  You just can't use an abstract class.

If you use an interface, MEF will only include exports that have metadata keys corresponding to your view interface properties.  There is no guarantee that the values are of the correct type though, and if they aren't you will get an InvalidCastException when you attempt to access the metadata view.

Does this help clear things up?

Daniel
Dec 3, 2008 at 5:07 AM
That said, the issues that you are bringing up sound incredibly familiar - we had a very long conversation wrt whether we should support metadata views other than interfaces. We came very close to supporting custom metadata views, but ended up nixing tyem, mainly because we felt the need for them weren't enough scenarios to justify their existance.
Could you explain why you would want to use types other than interfaces for metadata views?
Dec 3, 2008 at 5:48 AM
Well that doesn't mean that option (2) from above works even though a strongly-typed interface is included in the attribute definition. I still think there's still a case where there are multiple ways of providing metadata (strongly-typed metadata interface using the MetadataType parameter on the ContractType attribute, and using a MetadataAttribute decorated Attribute implementation) but the framework doesn't make them interchangable as the Export<T, TMetadata> scenario proves -the framework differentiates between the two. I'm tempted to say it's a design flaw :-)


From: notifications@codeplex.com
To: andreas@selfinflicted.org
Date: Tue, 2 Dec 2008 16:37:07 -0800
Subject: Re: CardinalityMismatchException [MEF:41266]

.ExternalClass {font-family:Verdana;font-size:0.75em;} .ExternalClass #EC_ThreadNotificationFooter {border-top:1px solid #ccc;color:gray;} .ExternalClass #EC_ThreadNotificationPostBody {margin-bottom:2em;} .ExternalClass {font-family:Verdana;font-size:0.75em;} .ExternalClass #EC_ThreadNotificationFooter {color:gray;border-top:1px solid #ccc;} .ExternalClass #EC_ThreadNotificationPostBody {margin-bottom:2em;} From: dsplaisted
How the metadata is supplied and how it is consumed are not directly related. In between them there is always an IDictionary<string, object> which represents the metadata. So it doesn't matter if you specify the metadata on your export viea the ExportMetadataAttribute or via a custom attribute with the MetadataAttributeAttribute on it. You can still use an interface on the consumer side. You can also use the untyped dictionary if you like. You just can't use an abstract class.

If you use an interface, MEF will only include exports that have metadata keys corresponding to your view interface properties. There is no guarantee that the values are of the correct type though, and if they aren't you will get an InvalidCastException when you attempt to access the metadata view.

Does this help clear things up?

Daniel
Read the full discussion online.
To add a post to this discussion, reply to this email (MEF@discussions.codeplex.com)
To start a new discussion for this project, email MEF@discussions.codeplex.com
You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on codePlex.com.
Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com
Dec 3, 2008 at 5:50 AM
I thought my example above, where u have conctrat types which you do not own, thus you cannot decorate them with a ContractType(MetadataType(typeof(...))) attribute was enough? I think a UserControl is a prime example of there supplying meta data with the help of the attribute is the only way that you can supply any sort of meta data ... though that doesn't appear to be fully supported in MEF since i differentiated between the two methods (see my reply above this one as well)


From: notifications@codeplex.com
To: andreas@selfinflicted.org
Date: Tue, 2 Dec 2008 21:08:07 -0800
Subject: Re: CardinalityMismatchException [MEF:41266]

.ExternalClass {font-family:Verdana;font-size:0.75em;} .ExternalClass #EC_ThreadNotificationFooter {border-top:1px solid #ccc;color:gray;} .ExternalClass #EC_ThreadNotificationPostBody {margin-bottom:2em;} .ExternalClass {font-family:Verdana;font-size:0.75em;} .ExternalClass #EC_ThreadNotificationFooter {color:gray;border-top:1px solid #ccc;} .ExternalClass #EC_ThreadNotificationPostBody {margin-bottom:2em;} From: olegl
That said, the issues that you are bringing up sound incredibly familiar - we had a very long conversation wrt whether we should support metadata views other than interfaces. We came very close to supporting custom metadata views, but ended up nixing tyem, mainly because we felt the need for them weren't enough scenarios to justify their existance.
Could you explain why you would want to use types other than interfaces for metadata views?
Read the full discussion online.
To add a post to this discussion, reply to this email (MEF@discussions.codeplex.com)
To start a new discussion for this project, email MEF@discussions.codeplex.com
You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on codePlex.com.
Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com
Dec 3, 2008 at 10:03 PM
Actually, you know what? The coin sort of just dropped for me. I can do exactly what I need to and dsplaisted was trying to tell me about it before, unfortunate for me I didn't quite grasp the true nature of what he was saying. For anyone that may have read this thread, this is how you should be using it

1) Define an interface for your metadata

public interface IMyMetadataInterface
{
 Version Version { get; set; }
}


2) Define an attribute, implement your medadata interface on it and decorate the class with the MetadataAttribute

[MetadataAttribute]
class MyMetadataAttribute : Attribute, IMyMetadataInterface
{
 public MyMetadataAttribute(string version)
 {
  this.Version = new Version(version);
 }

 public Version Version { get; set; }
}


3) Decorate your parts with the attribute

[Export(typeof(MyContractType))]
[MyMetadataAttribute("1.0.0.0")]
public class MyContractType
{
  ...
}


4) Use the interface when you provide the metadata conditions

[Import(typeof(MyContractType))]
public Export<MyContractType, IMyMetadataInterface> Service { get; set; }


The bug I was experience was the result of using the attribute as the type of the TMetadataView type parameter for the Export<T, TMetadataView> class.

Funny fact! In the above example, the MyMetadataAttribute class doesn't have to implement the IMyMetadataInterface inorder for this to work. MEF appears to do a structural comparison to check if the provided metadata conforms to the members which belong to the type passed to the TMetadataView type parameter. However its a good practise to implement the interface on your attribute as well! :-)

Dec 4, 2008 at 9:08 PM

We are doing some dynamic construction for the metadata views. The attribute is not required to implement the interface.

From: TheCodeJunkie [mailto:notifications@codeplex.com]
Sent: Wednesday, December 03, 2008 2:03 PM
To: Glenn Block
Subject: Re: CardinalityMismatchException [MEF:41266]

From: TheCodeJunkie

Actually, you know what? The coin sort of just dropped for me. I can do exactly what I need to and dsplaisted was trying to tell me about it before, unfortunate for me I didn't quite grasp the true nature of what he was saying. For anyone that may have read this thread, this is how you should be using it

1) Define an interface for your metadata

public interface IMyMetadataInterface
{
Version Version { get; set; }
}


2) Define an attribute, implement your medadata interface on it and decorate the class with the MetadataAttribute

[MetadataAttribute]
class MyMetadataAttribute : Attribute, IMyMetadataInterface
{
public MyMetadataAttribute(string version)
{
this.Version = new Version(version);
}

public Version Version { get; set; }
}


3) Decorate your parts with the attribute

[Export(typeof(MyContractType))]
[MyMetadataAttribute("1.0.0.0")]
public class MyContractType
{
...
}


4) Use the interface when you provide the metadata conditions

[Import(typeof(MyContractType))]
public Export<MyContractType, IMyMetadataInterface> Service { get; set; }


The bug I was experience was the result of using the attribute as the type of the TMetadataView type parameter for the Export<T, TMetadataView> class.

Funny fact! In the above example, the MyMetadataAttribute class doesn't have to implement the IMyMetadataInterface inorder for this to work. MEF appears to do a structural comparison to check if the provided metadata conforms to the members which belong to the type passed to the TMetadataView type parameter. However its a good practise to implement the interface on your attribute as well! :-)

Read the full discussion online.

To add a post to this discussion, reply to this email (MEF@discussions.codeplex.com)

To start a new discussion for this project, email MEF@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com