Dynamic Import/Export contracts

Oct 2, 2009 at 10:52 PM

Hello folks,

is there some possibility to actually decide at runtime which export fits into an import ?

Lets assume two booleans marked as exports, each with its unique contract.

On the other side we have 2 boolean import slots.

Is it possible to actually give those 2 imports a contract at runtime, to be able to decide which export will be imported by which slot ?

Developer
Oct 3, 2009 at 12:09 AM

You cannot give a Import a contract name at runtime. Instead to accomplish the goal you want you will have the Exports have the same contract name and Import all of that contract and use metadata to pick the correct Export.

For Example:

[Export(typeof(IFoo))]
[ExportMetadata("Type", "type1")]
public class Type1 : IFoo {  }

[Export(typeof(IFoo))]
[ExportMetadata("Type", "type2")]
public class Type2 : IFoo {  }

interface IFooMetadata {
   string Type;
}

...
[ImportMany]
public Lazy<IFoo, IFooMetadat>[] Foos;
...
foreach(var foo in Foos)
   if (foo.Metadata.Type == "type1") 
      // do work with foo

I hope that makes since.


Oct 6, 2009 at 7:16 AM

you can use filtered catalog (read more about it here)

Oct 6, 2009 at 7:18 AM

you can use metadata (see the this sample)

Oct 21, 2009 at 10:16 PM

Hello,

I read your posts about filtered catalagues and the usage of metadata to uniquely tag and filter exports. Thanks for the posts btw. ;)

While both ways work perfectly to filter the exports, they still require me to manually set the imports, since I dont want them all to be inserted into a single list.

Both ways require me to create a sink for all possible exports and then direct them manually to their destinations, using a loop and some comparisons as shown in the example above.

Is there no way to tag the imports and override some methods inside the CompositionContainers to provide a direct link from an export directly to the final import destination.

For example:

 

[Export(typeof(IFoo))]
[ExportMetadata("Type", "type1")]
public class Type1 : IFoo {  }

[Export(typeof(IFoo))]
[ExportMetadata("Type", "type2")]
public class Type2 : IFoo {  }

interface IFooMetadata {
   string Type;
}

... 

[Import]
// The export with MetaData "Type1" should go here
public IFoo Type1 { get; set; }

[Import]
// The export with MetaData "Type2" should go here
public IFoo Type2 { get; set; }


Hope this makes sense.

Alex

 

 

Oct 21, 2009 at 11:03 PM

Hi

Yes there actaully is a recent found approach which doesn't require changing the container at all. You can do it in a custom container, but that requires the host to know about it. The approach uses metadata views to allow different filters and even works with single imports. The one thing that it requires is lazy on the import side. The plus side is it comes close to metadata value filtering.

Baically the way it works is this. In your example you've got metadata for Type1 and Type2. In this case you are wanting to filter by metadata value. Unfortunately, without a bunch of customization, you can't.

What you CAN do though is use the presence of a KEY as a way to filter on a metadata view. This works because MEF will filter imports that use a metadata view based on exports that actually contain those metadata keys.

So what you can do is this (using your example):

public interface IFooType1Metadata {
  bool Filter_FooType1 {get;}
}

public interface IFooType2Metadata {
  bool Filter_FooType2 {get;}
}

[Export(typeof(IFoo))]
[ExportMetadata("Filter_FooType1",true)]
public class Foo1 : IFoo {
}

[Export(typeof(IFoo))]
[ExportMetadata("Filter_FooType2",true)]
public class Foo2 : IFoo {
}

public class UsesFooType1 {

  [Import]
  Lazy<IFoo,IFooType1Metadata> Foo {get;set;}
}

public class UsesFooType2 {
  [Import]
  Lazy<IFoo,IFooType2Metadata> Foo {get;set;}

}

 When the above parts get composed, UsesFooType1 will get Foo1, and UsesFooType2 will get Foo2. They both use the SAME contract, but different metadata views. This works with collections as well.

The only caveat is the need for the metadata view and the dummy property which serves no other benefit but for filtering. However though it might feel a little hackish, it actually is using a fully supported MEF feature that was part of our metadata view design.

Thoughts?

 

  

Oct 21, 2009 at 11:14 PM

Hey Glenn!

I thought that was very hackish indeed! I've often wished for a way to filter imports sharing the same contract. But using different meta data types and dummy properties. No - not for me.

Cheers,

M.

Oct 21, 2009 at 11:46 PM

Hi,

your example almost fits our requirements, unfortunately a small issue still exists and the more I read the less I'm convinced its even possible.

In our project the information which export fits into which import slot is not known at designtime but at runtime.

Before stumbling over MEF, we planned to create a very similar environment in which we'd tag import and exports with a unique id and their type.

The merge class would then receive a list of id pairs, from which the resulting classes could be assembled.

Since you cannot change metadata at runtime, the only possibility left to explore would be to alter the merging process, making it somhow configurable from the outside, is this possible ?

 

 

 

Oct 21, 2009 at 11:51 PM

If you want something more sophisticated, you can do it certainly. It requires either a custom container, a custom EP or a custom catalog. You'll need some way to track state related to the request (maybe thread local storage) and then you can apply a custom filtering policy on what exports get returned at any point in time.

Oct 22, 2009 at 12:06 AM

Filtering the exports seems pretty straight forward, but I could not figure out how to control the imports.

Since the container merges the catalogs and or batches it's pretty safe to assume thats the sweetspot I have to customize, unfortunately theres only a single method "GetExportsCore" to override.

The name suggests an internal filter, but I could not really figure out how to alter this in the proper way.

 

Alex

Oct 22, 2009 at 12:09 AM

@noopman

I am not going to argue about whether it's hackish or not :-).

The advantage is it provides a very clean way (once you get past the hack) to accomplish the filter. With custom MEF export attributes and such you can hide it completely for the exporter, and for the importer they simply need to know about a view. The other advantage is it is 100% MEF compliant code that will work in any environment, as long as the parts are present. This is one of our fundamental design goals for MEF, that parts are COMPLETELY resusable, meaning no host config (of any sort) required.

Glenn

 

Oct 22, 2009 at 12:13 AM

@Alex

You can't filter imports directly without a significant amount of work. What you can do though is have state information which the container uses to then apply a filter on the exports for the importer :-)

You can also use container hiearchies as a mechanism to control which exports are available for a specific part instance. This requires you though to manage creating the container hierarchies.

HTH

Glenn

Oct 22, 2009 at 12:53 AM

@noopman and @Alex

I feel your pain and I tried 3 different methods of doing this exact thing. The latest being the one gblock is describing. Yes it is hackish and yes it is compliant with current MEF. and It doesnt scale well since you need 1 interface and 1 metadata for each category. Can get messy fast.

When I come up with another alternative I'll make a blog post and describe all the ways of categorizing Imports as I see it today.

 

Robert

Oct 22, 2009 at 12:59 AM
krasshirsch wrote:

In our project the information which export fits into which import slot is not known at designtime but at runtime.

 

 We have explored some options in this area. Would you mind sharing more about your application and its scenarios? That would be greatly appreciated.

Oct 22, 2009 at 2:44 PM
Edited Oct 23, 2009 at 1:01 AM
haveriss wrote:
krasshirsch wrote:

In our project the information which export fits into which import slot is not known at designtime but at runtime.

 

 We have explored some options in this area. Would you mind sharing more about your application and its scenarios? That would be greatly appreciated.

Of course,

we are currently developing a workflow system on a small scale.

Since it is a very tedious job to rewrite code each time the smallest change in the workflow chain arises, we came up with the idea to create a losely bonded workflow system.

The idea is to have small individual modules, we call them "atoms", which consists of a logical core that performs some task like sending an email and of course external import and export slots, ususally properties.

Once this small "atoms" are coded and compiled they can be combined into a large workflow by just dragging and dropping the import to the export slots, without having to recompile the whole system.

Since the combination of import and export slot is handpicked by the user, it can vary from workflow to workflow and is therefor unknown at designtime.

Hope this makes any sense to you.

Alex

 

Oct 22, 2009 at 6:12 PM
krasshirsch wrote:

Once this small "atoms" are coded and compiled  they can be combined into a large workflow by just dragging and dropping the import to the export slots, without having to disassemble the whole system.

Since the combination of import and export slot is handpicked by the user, it can vary from workflow to workflow and is therefor unknown at designtime.

Hope this makes any sense to you.

Alex

 

 Alex, I dont know your design but I would think you would have some sort of controller, lets call it Workflow. Workflow would gather up (Import )all the atoms that are exported from your assemblies. At runtime you have different lists for Import and Export which you would manage like any other list of objects.

 

Oct 23, 2009 at 1:26 AM

The point is, gathering up all available imports and exports isn't the hard part, reassembling them with all their relations intact is.

While MEF does allow to filter the exports, AFAIK there's currently no way to have 2 Imports with identical contracts consume 2 different exports without the additional overhead mentioned above.

My first idea was extending the currently implemented attributes by a ImportMetaDataAttribute which would serve the same purpose as the ExportMetaData but on the import side.

Unfortunately I have no access to the methods inside the container that merge imports and exports ... so this idea died pretty fast.

 

Alex

 

 

Oct 23, 2009 at 7:26 AM

Alex

It sounds like what you are trying to do is explicitly wire up component hierarchies using MEF, where the contracts themselves are not enough to determine what goes where. Is that correct?

Glenn

Oct 23, 2009 at 11:43 AM

Exactly, 

most components are kept as simple as possible. The "if" branch for example has only a single boolean in.

Obviously this component wont be the only one with a boolean contract, so we need a finer seperation method.

Alex

Oct 23, 2009 at 7:21 PM

@Alex

I understand what you want to do and I agree in principal but in your specific case what I meant was have one controller gather up all the Parts. Then at runtime manage your workflow without even thinking about the MEF model. Since you want a wireup at runtime and to be able to change the connections and flow at runtime I would suggest you just handle this with whatever logic you need to do this and just use MEF for descoverablity.

Below is basically what I am talking about. Now I dont know your needs specifically so this might not be close but its only to show the idea. Manage the discoverablity of your Atoms separate from building your Workflows. A workflow has one or more Atoms in your In and Out which you control during runtime. MEF would not be involved at this point.

public class WorkflowEngine

{

  [Import(AllowRecomposition=true)]

  public Collection<Atom> Atoms {get; set;}

public Collection<Workflow> Workflows {get; set;}

}

public class Workflow

{

public Collection<Atom> In {get; set;}

  public Collection<Atom> Out {get; set;}

}

 

 

Oct 23, 2009 at 9:03 PM

@robertkozak

Your suggestion reflects our current state, using MEF as a simple part discovery tool and merging ins and outs using a custom project.

The downside is we'd need to recreate a lot of logic that MEF already supplies out of the box.

Oct 23, 2009 at 9:22 PM
Edited Oct 23, 2009 at 9:23 PM

I agree with Robert and I know of at least one big customer doing this now. MEF is great for discovering an unknown set of things. It's great for assembling those things by contract. It's not really designed to assemble them in a specific configuration of implementations of specific contracts. If you try to do it MEF, it will get ugly very quicky.

An alternative is you use a builder approach. You use MEF to provide the components used by the builder. The builder puts them together. The way to get specific implementations is to use metadata. As an example each export can have a piece of metadata called "AtomtID". The builder can use those ids to locate the specific export implementations you are expecting to wire-up. The builder will likely import factories rather than actaul implementations. The reason for this is that it will need to create dynamic instances on the fly based on the configuration it was given.

True as you mention, it does require composition logic in the builder. However, that logic is domain-specific rather than general purpose like MEF. The benefit is it lets you achieve explicit wiring without bending over backwards to hack it into MEF.  Also it does not have to duplicate all logic. For example each atom CAN have imports provided to it if those imports are purely based on contract. The factory would simply use the container to create the atoms making that possible.

Make sense?

Glenn

 

 

 

Oct 24, 2009 at 11:46 AM

I thank you all for your quick responses and suggestions.

Although we obviously can't use MEF to assemble our workflows, it will still play a major part in our projects, since we plan to use it throughout our plugin system.

Keep up your excellent work, Alex ;)