Trouble with Metadata and Custom ExportAttribute

Sep 18, 2009 at 12:49 AM
Edited Sep 18, 2009 at 12:50 AM
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
    public class CommandAttribute : ExportAttribute
    {
        public CommandAttribute() : this(null, null) { }
        public CommandAttribute(string module) : this(null, module) { }

        public CommandAttribute(string name, string module): base(name, typeof(ICommand))
        {
            Name = name;
            Module = module;
        }

        public string Name { get; set; }
        public string Module { get; set; }
        //public string Title { get; set; }
        //public string Description { get; set; }
        //public string Icon { get; set; }
        [DefaultValue(0)]
        public int Order { get; set; }
    }
This attribute works perfectly fine for this export and import:
[Command("TestCommand", "Test")]
public ICommand TestCommand1 { get; set; }
[Import("TestCommand")] 
public ICommand TestCommand { get; set; }
If I uncomment out Title, Description or Icon it fails with a null Reference Exception in the Metadata property.
Also the import to a public Lazy<ICommand, ICommandMetadata> CommandItems {get; set;} never gets satisfied. 
Sep 18, 2009 at 1:09 AM

If ICommandMetadata contains Title, Description and Icon, then commenting out those properties on the CommandAttribute will cause all commands to get ignored unless Title, Description and Icon are optional  (marked with a [DefaultValue()] attribute). Properties in the metadata view cause an implicit filtering to take place on the parts which ensures that all required metadata is present.

 

Sep 18, 2009 at 3:59 PM

Just to add to Glenn's suggestion: the AllowMultiple property on AttributeUsage should also be set to false in order for this to work.

Nick

Sep 18, 2009 at 5:12 PM

Nick,

I cannot use AllowMultiple=true at all? I was planning on using a Category property and allowing the same command to be used in different areas like a NavBar, ToolBar or Button.

Sep 18, 2009 at 5:14 PM

I had also commented them out in the ICommandMetedata interface, and I did try [DefaultValue("")] on them. But I'll try again and see what happens.

Sep 18, 2009 at 5:18 PM

You can use it, but if you do, each metadata item will need to be accessed via an array in the metadata view, and I don't think there will be a way to match up the { Name, Module, Order } tuples.

E.g:

interface ICommandMetadata {

  string[] Name { get; }

  int[] Order { get; }

  // ... etc
}

If you really want AllowMultiple-type functionality, it is best to use a regular MetadataAttribute for the items that you want many of, rather than a subclass of ExportAttribute. Just implementation edge-cases.

Sep 18, 2009 at 6:28 PM
Edited Sep 18, 2009 at 6:28 PM

Nick,

Thanks, I'll play around and see what I can come up with.

Also I think Im confused with the [ImportMany]. I have a unit test where I am testing this custom ExportAttribute. Now that I added the [DefaultValue()] I no longer get the Null Reference Exception and I changed the AllowMultiple to false.

Ok given this Export

[Command("TestCommand", "Test", Description = "This is a sample description.")]
public ICommand TestCommand1 { get; set; }

The following Imports all resolve:
[Import("TestCommand")]
public ICommand TestCommand { get; set; }
[ImportMany("TestCommand")]
public Lazy<ICommand, ICommandMetadata>[] TestCommands { get; set; }
 [ImportMany("TestCommand")]
 public ICommand[] TestCommands { get; set; }

Thee follwowing do not resolve.

[ImportMany]
public Lazy<ICommand, ICommandMetadata>[] TestCommands { get; set; }

[ImportMany]
public ICommand[] TestCommands { get; set; }
        
[Import]
public ICommand TestCommand { get; set; }


Does this mean if I give a Contract Name I can only import via the contract name?
Sep 18, 2009 at 6:57 PM

Hi Robert,

That's correct - the contract name is the key, so exporters and importers must agree upon it.

Cheers,
Nick

Sep 18, 2009 at 8:21 PM

Ok thats clear. :)

Is there any way I can write a Custom ImportAttribute or ImportManyAttribute that will resolve based on a criteria? Can I say "Give me all ICommand types that have this Category name or Module name?" Is there a hook point anywhere?

Developer
Sep 18, 2009 at 10:23 PM

No currently there isn't any way to create a custom ImportAttribute that will let you give an open ended constriant for how to find the matching export. Using the default attributed model (including custom import/export attributes) Imports and Exports must have a matching contract name.

Now you could actually stop passing the name to the base contructor in your CommandAttribute and thus the contract name would be typeof(ICommand) and then you could Import all ICommands and filter that list according to the metadata. However the filtering would be manual and not happen automatically by MEF.

We are considering more open ended import constraints in the future but at this point you sort of have to pick which approach works best for your scenario.

Sep 18, 2009 at 11:10 PM

Yeah I kinda figured that and I started on creating a FilteredCollection<TContract, TMetadata> but I'm not really liking this implementation. I see there are ExportProviders in MEF are you thinking about doing ImportProviders?

Should I considering trying to add this or is it too much work based on MEF preview 7 today? I haven't spent a lot of time looking over the MEF source code so I don't even know if this is possible but assuming it is are there any pitfalls I need to watch out for?

Developer
Sep 20, 2009 at 6:00 AM

I'm not entirely sure what you mean by ImportProviders. Who would be providing imports and who would they be providing them to? Currently Imports are defined on ComposablePart[Definitions] as ImportDefinitions and those are provided by ComposablePartCatalogs. 

Currently the core method of an ExportProvider is GetExports(ImportDefinition) which takes an import definition. That import definition in general can contain any constraint you like it to contain however when you use the attributed programming model then that constraint will always contain a test for the contract name.

Sep 21, 2009 at 5:23 PM

I dont see how the attributed programming model has to "always contain a test for contract name" unless it is baked in and not extendable. For example I want to say : ImportMany ICommand of Category XYZ. I would think that by using metadata and some IF tests I should be able to import extactly what I want.

Right now I import everything based contract type and filter using a filtered collection. I would much rather have it filtered as part of the importing so I dont need to do it after the fact. The best way to do this is still lost on me because I am still learning MEF.

Developer
Sep 21, 2009 at 6:48 PM

The contract name test is currently baked into the attributed programing model and isn't currently extendable. Keep in mind by attributed programming model I essentially mean the translation from ImportAttribute to ImportDefinition. In our translation the contract name is always part of the constraint that is included in the ImportDefinition. The biggest problem here is how you would embed those "IF tests" in an attribute declaration. Sure you could consider coding the logic in a custom import attribute but then you would need a custom import attribute for every such speciallization. Which isn't much different then actually creating a custom collection object today which has the same custom logic and importing into it.

That said we are considering opening the door for a more generalized translation of the ImportAttribute into the ImportDefinition in the future but that will not be part of our .Net 4 release.

As for the best way to do this right now I think what you are doing is reasonable but if you want to reuse the same logic you can create a custom collection object that could do the filtering itself and import directly into it. If you want an example of this see Glenn's AdaptingCollection at http://mef.codeplex.com/Thread/View.aspx?ThreadId=64387.

Sep 21, 2009 at 7:24 PM

Glenn's AdaptingCollection looks a lot like my FilteredCollection.

And I like Ray's approach for filter

[ImportMany(Filter = typeof(MyFilter)]
public Lazy<IMessenger, IMessengerMetadata> Messenagers { get; set; }

Which is similar to what I want. For now I'll use my collection but I would love to see more extensiblity in MEF :) so I can be declaratively more expressive.

Sep 21, 2009 at 7:35 PM

We are looking into several approaches for doing this in the future, including having attributes that accept expressions. Having a filter type is also an interesting approach.

Thanks

Glenn

From: robertkozak [mailto:notifications@codeplex.com]
Sent: Monday, September 21, 2009 12:25 PM
To: Glenn Block
Subject: Re: Trouble with Metadata and Custom ExportAttribute [MEF:69324]

From: robertkozak

Glenn's AdaptingCollection looks a lot like my FilteredCollection.

And I like Ray's approach for filter

[ImportMany(Filter = typeof(MyFilter)]
public Lazy<IMessenger, IMessengerMetadata> Messenagers { get; set; }

Which is similar to what I want. For now I'll use my collection but I would love to see more extensiblity in MEF :) so I can be declaratively more expressive.

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