Single Part and deciding which Part to use

Sep 11, 2008 at 12:18 AM
(I think Part is the right terminology)

Hi,

This relats to my previous post on unloading addins. I was thinking about using MEF for something like a logger,  bascially, I want my application to have a default logger (a text file) but allow this logger to be replaced by custom loggers.

As such, I have this code which classes use to access the logger.

[Import]
public ILogger logger { get; set; }

I'm using the DirectoryPartCatalog, when it detects a new logger I wanted it to override my default logger, or the current logger, as I only want one logger available at any time.

However, instead of throws a CompositionException.

System.ComponentModel.Composition.CompositionException was unhandled
  Message="The composition produced 2 composition issues. The first few issues of severity level 'error' are provided below. Review the Issues collection for more detailed information.\r\nCM:TooManyExportsThatMatchConstraint : Error : Multiple exports were found that match the constraint '(composableItem.ContractName = \"MEF_WinFormsApp.Interfaces.ILogger\")'. The import for this contract requires a single export only.\r\nCM:TooManyExportsThatMatchConstraint : Error : Multiple exports were found that match the constraint '(composableItem.ContractName = \"MEF_WinFormsApp.Interfaces.ILogger\")'. The import for this contract requires a single export only.\r\n"
  Source="System.Windows.Forms"
  StackTrace:
       at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
       at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
       at System.Windows.Forms.WindowsFormsSynchronizationContext.Send(SendOrPostCallback d, Object state)
       at Microsoft.Internal.SynchronizationContextHelpers.ExecuteOnCreationThread(SynchronizationContext syncContext, SendOrPostCallback callback) in c:\Root\Code\MEF\Drops\MEF_Drop_2008_09_04\MEF_Drop_2008_09_04\src\ComponentModel\Microsoft\Internal\SynchronizationContextHelpers.cs:line 15
       at System.ComponentModel.Composition.AggregatingComposablePartCatalog.OnChanged(Object sender, ComposablePartCatalogChangedEventArgs e) in c:\Root\Code\MEF\Drops\MEF_Drop_2008_09_04\MEF_Drop_2008_09_04\src\ComponentModel\System\ComponentModel\Composition\AggregatingComposablePartCatalog.cs:line 0
       at System.ComponentModel.Composition.ComposablePartCatalogCollection.Add(ComposablePartCatalog item) in c:\Root\Code\MEF\Drops\MEF_Drop_2008_09_04\MEF_Drop_2008_09_04\src\ComponentModel\System\ComponentModel\Composition\ComposablePartCatalogCollection.cs:line 76
       at System.ComponentModel.Composition.DirectoryPartCatalog.AddAssemblyGuarded(String assemblyFileName) in c:\Root\Code\MEF\Drops\MEF_Drop_2008_09_04\MEF_Drop_2008_09_04\src\ComponentModel\System\ComponentModel\Composition\DirectoryPartCatalog.cs:line 72
       at System.ComponentModel.Composition.DirectoryPartCatalog.OnFileCreated(Object sender, FileSystemEventArgs e) in c:\Root\Code\MEF\Drops\MEF_Drop_2008_09_04\MEF_Drop_2008_09_04\src\ComponentModel\System\ComponentModel\Composition\DirectoryPartCatalog.cs:line 218
       at System.IO.FileSystemWatcher.OnCreated(FileSystemEventArgs e)
       at System.IO.FileSystemWatcher.NotifyFileSystemEventArgs(Int32 action, String name)
       at System.IO.FileSystemWatcher.CompletionStatusChanged(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* overlappedPointer)
       at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
  InnerException:



How do I get around this to meet my requirements? How can I control this?

Thanks

Ben
Blog.BenHall.me.uk

Sep 11, 2008 at 1:22 AM

Thanks Ben for trying out MEF!

Yes, this is by design. When you ask for only one logger, but there are multiple existing, container cannot decide which one your want.

When you expect there could be more than one logger, you should ask container to give you all loggers it find instead of just one. Then you can pick the one you want to use. Code could look like below:

[Import]
public ExportCollection<ILogger> Loggers {set; get;}

This way you are actually asking for a collection of ILogger and you also get extra benefit. You can use metadata on your different loggers to distinguish them and load only the one you want to use (aka. delay loading). Your loggers can look like below

[Export(typeof(ILogger))]
[ExportMetadata("Type", "TextLogger")]
public class Logger1 : ILogger {...}

[Export(typeof(ILogger))]
[ExportMetadata("Type", "DatabaseLogger")]
public class Logger2 : ILogger {...}

Hope this helps. Please let us know if you have any questions.

Thanks,
Zhenlan

Sep 11, 2008 at 2:57 PM
Zhenlan,
if we get all the loggers and then choose which one we want, then are we not loading objects without using them?
My suggestion is to set a marker field that would exactly match the logger one want to use.

with that approach I could say for example,
get me a logger that will log to a database. If there is one part that supports database logging then it will be instantiated. This should be done with MEF and the calling app would only say which logger it wants to use. I guess that is what the Export metaData attrib does


Another approach I can think of is tell the MEF to describe all the loggers it supports and then the application decides on which logger to use at what point of time.
Sep 11, 2008 at 11:01 PM
Hi all,

Another thing that worries me with using the DirectoryPartCatalog is a security issue.
What if someone drops an assembly into the directory that contains malicious code. The trusted assembly could easily be swapped out.
What is your recommendation for this scenario?

Or if I don't consider using the collection approach on an Import, any additional assembly with an already existing Export will crash the application. Even with a metadata declaration I can't see a way to prevent this.

Ciao,
Jens
Sep 12, 2008 at 3:23 AM

ganesh,

I am not sure I understand you correctly about "marker field". If you mean a field in the logger class, to use it, you have to instantiate all your loggers. However, with MEF, you only need to instantiate the one you want to use. The code below explains this idea

// Find all database loggers
var dbLoggers = Loggers.Where(logger => logger.Metadata.Keys.Contains("Type")).Where(logger => logger.Metadata.Values.Contains("DatabaseLogger"));
// Only ask the instance of the logger you want to use. Other loggers will not be instantiated.
if (dbLoggers.Any())
{
    var dbloggerObject = dbLoggers.First().GetExportedObject();
    // ... do work ...
}

If you use strongly-typed metadata, the code above can be much simpler. I will leave that to you to find out. But please let us know if you have any questions.

Thanks,
Zhenlan

Sep 12, 2008 at 2:41 PM
I am with Jens on the malicious code. That is why in fredriknormen's Blog I had suggested a registering model should be incorporated. The dependency should be on the configuration file.

To answer, no actually to question again, Zhenlan's reply. IMO, should the "discovery code" for a part,needed by an application, be left to MEF. By this I mean, that the MEF should provide a way of enuming the parts available and then allow me to set that as a prop and instantiate an object of the type enumed.

for example,
As an app developer,
I would write something like this,

Loggers.Part = Part.Logger.Database // this is the enum

myLoggers = Loggers.getParts();  / /There might be more than one database logger. This can be finegrained by making the enum more granular.

the rest of the code that zhenlan had written should, IMO, go into the MEF. This way it will also provide a stricter way of adding parts. Only those parts enumed can be loaded.
 
Sep 12, 2008 at 4:29 PM
@ganesh, jens

You can add screening and filtering at the catalog level. Catalogs are the entry point in to MEF. You can for example create your own SecureDirectoryWatchingCatalog which will verify any assemblies that are loaded before they are loaded. At this point our catalogs themselves are not really extensible so you'd have to copy our DirectoryWatchinCatalog and maybe add a virtual override. However, I have filed a bug to make sure we design in proper extensibility points.

Thanks
Glenn
Sep 12, 2008 at 5:15 PM
Edited Sep 14, 2008 at 8:55 PM
@Ben

Another approach to solving your issue is to use a property export. In the property you can filter down the one logger you want, and then just export it. The only caveat here would be you need two contracts, one for the individual loggers (I used a string) and one for the single logger that is exported.. Building off of Zhen's example. Below is a unit test that demonstrates this. Each logger exports the "Logger" contract. Then in my LoggingProvider which is a singleton, I import the loggers, parse out the one i want (I just took the first) and re-export it as an ILogger contract. It's transparent to the user.

Now a better approach is to actually create a custom resolver. Every export that is queried on the contianer goes throught the resolver. Within the resolver you can implement whatever policies you like. However, writing a resolver is not always an option. Also today it's not as clean as it could be. We are planning some signficant refactoring / clean up that will make resolvers much easier to author.

 

[TestClass]

  public class LoggerTest  

  {

    [TestMethod]

    public void LoggingProviderReturnsLogger()

    {

      AttributedAssemblyPartCatalog cat = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());

      CompositionContainer cc = new CompositionContainer(cat.CreateResolver());

      var logger = cc.GetExportedObject<ILogger>();

      Assert.IsNotNull(logger);

    }

  }

 

  public interface ILogger  

  {

  }

 

  [Export("Logger")]

  [ExportMetadata("Type", "TextLogger")]

  public class Logger1 : ILogger  

  {

  }

 

  [Export("Logger")]

  [ExportMetadata("Type", "DatabaseLogger")]

  public class Logger2 : ILogger  

  {

  }

 

  [CompositionOptions(CreationPolicy=CreationPolicy.Singleton)]

  public class LoggingProvider  

  {

    private ILogger _theLogger;

 

    [ImportingConstructor]

    public LoggingProvider([Import("Logger")] ExportCollection<ILogger> loggers)

    {

      _theLogger = loggers.First().GetExportedObject();

    }

 

    [Export]

    public ILogger Logger

    {

      get { return _theLogger; }

    }

  }

 

Sep 12, 2008 at 6:16 PM
It's really nice to see nowhere referenced but working classes :)
Sep 17, 2008 at 12:41 AM
Edited Sep 17, 2008 at 12:44 AM
Hi Glenn,

Interesting code. How does GetExportedObject know about the LoggingProvider? Is it because it has [Import("Logger")] as part of the constructure and because it is a singleton?

It doesn't seem happy (it's always null) with me doing this:

[

Import("Logger")]
public ILogger logger { get; set; }

 

 



Thanks
Sep 17, 2008 at 4:29 AM
Edited Sep 17, 2008 at 4:44 AM

In the following two classes if I remove the first letter (to make them both "Logger") it complains about there being two Exports of "Logger".

    9 namespace MEFContrib.Library.Loggers

   10 {

   11     [Export("MLogger")]

   12     [ExportMetadata("Type","MessageBoxLogger")]

   13     public class MessageBoxLogger : ILogger

   14     {

   15         public void Log(string message,

   16             LogCategoryType category, LogPriortyType priority)

   17         {

   18             MessageBox.Show(message,

   19                 string.Format("{0}({1})", category, priority));

   20         }

   21     }

   22 }


   10 namespace MEFContrib.Library.Loggers

   11 {

   12     [Export("DLogger")]

   13     [ExportMetadata("Type","DebugLogger")]

   14     public class DebugLogger : ILogger

   15     {

   16         public DebugLogger() { }

   17         public void Log(string message, LogCategoryType category, LogPriortyType priority)

   18         {

   19             Debug.WriteLine(message, string.Format("{0}({1})", category, priority));

   20         }

   21     }

   22 }


How would you define the Import Attributes (that utilize ExportMetadata)?

   19 namespace BasicWPFApp

   20 {

   21     /// <summary>

   22     /// Interaction logic for Window1.xaml

   23     /// </summary>

   24     [Export("MainWindow")]

   25     public partial class Window1 : Window

   26     {

   27         [Import("DLogger")]

   28         public ILogger DebugLogger { get; set; }

   29 

   30         [Import("MLogger")]

   31         public ILogger MessageBoxLogger { get; set; }

   32 

   33         public Window1()

   34         {

   35             InitializeComponent();

   36         }

   37 

   38         private void button1_Click(object sender, RoutedEventArgs e)

   39         {

   40             DebugLogger.Log("Click",

   41                 LogCategoryType.Debug, LogPriortyType.None);

   42         }

   43 

   44         private void button2_Click(object sender, RoutedEventArgs e)

   45         {

   46             MessageBoxLogger.Log("Click",

   47                 LogCategoryType.Debug, LogPriortyType.None);

   48         }

   49     }


I also found that if I move the Interface out of my .Library project into my .Interface project MEF complains during runtime - do I have to have my interface with my classes?

I would be interested in feedback on my BootStrapper class, i.e., if I'm violating any P&P for MEF.  Blogged about HERE - no news is good news ;)  My blog has the fully functional source code for the above code snippets.









Sep 17, 2008 at 11:59 AM
I had the same problem with two Imports for the same interface (just Import and a named Import catalog).  Very confusing and I only had a small sample.
Sep 17, 2008 at 12:38 PM
Edited Sep 17, 2008 at 12:42 PM
Stumbled onto Wes' puzzling blog HERE and found the answer(s).  On Sept 10, 2008 3:38 PM it states:

Q1: If you want to accept multiple you just import a collection of IMessageSenders.

I then examined the XFileExplorer views and noted that they have the same Export attribute, each with its own "index".   The Import pulls a collection (consistent with Glenn's unit test) which had me thinking perhaps this is what would supress the multiple "Logger" complaint;  I'll figure it out tonight and update my demo app (and blog).  

[

Export("Microsoft.Samples.XFileExplorer.StatusServiceContract")]
[
ExportMetadata("Index", 1)]

[
Import("Microsoft.Samples.XFileExplorer.FileExplorerViewContract")]
private ExportCollection<UserControl, IFileExplorerViewMetadata> _views = null;

 

 



Sep 17, 2008 at 4:15 PM
Zhenlan's  Sep 11 at 10:23 PM  reply is also to be taken into account if you want to selectively instantiate a particular class out of a list. The question is how would a application developer know what are the loggers available in a library built by the "library developers"?

Sep 17, 2008 at 4:33 PM
Bill

This is by design. MEF enforces cardinality. In your example, making both "Logger" means that you have 2 loggers. Your import is expecting a single, which one should MEF choose?

Traditional containers have a FIFO / LIFO model to choose which one to give. With MEF however, because of its discovery there is no way to choose the "First" one. Do you really want that someone dropped an assembly in the bin and the default immeidately changed without your knowing about it? It's the indeterminate nature of such a mechanism that has led us to shy away from supporing it. MEF systems have to be consistent regardless of what is dropped in the bin.

I understand the pain thougn, and we are exploring options.

1. Have a Default attribute that flags that export for use in a single cardinality import even if there are multiple. Problem here is what if 2 Loggers have [Default].....:)
2. Have a Default catalog (separate bin folder), and then have our resolver look in the default first always if there are single imports.

Open to ideas you guys have on how to solve this.

Thansk
Glenn
Sep 17, 2008 at 4:33 PM
Edited Sep 17, 2008 at 4:37 PM
@Ganesh,  Dough!  That'll teach me to read the entire message thread....  could have saved me tons of time last night...

Edited - saved same time GBlock did.
Sep 17, 2008 at 4:37 PM
Let me say clearly (and this doesn't necessarily represent the entire team). I am not a fan of you having to import the entire collection when you need one. It's feasible, but certainly not optimal. We need to come up with something (IMHO), but the  bar of success is that is must offer determinante behavior.
Sep 17, 2008 at 4:40 PM
All,

I am going to file a bug to make sure this doesn't fall of our radar.

Glenn
Sep 17, 2008 at 7:51 PM
I am still in .net 1.1/2.0
A quick question, is the Import and Export atributes available in .net 2.0?

and now the rest:
What if my classes are compiled into a separate dll? will MEF scan through all dlls in a bin folder to figure out which are the extensibility compatible dlls?
What if I want them in different folders, say for the sake of maintainability?
Why not have an option to tell an app which dlls to look for extensibility like a key value pair in a config file?
How about at development time MEF will let me know what classes are available as extensions for a particular type?

How will I know if the production extensibilities are the same as the stage and dev env? How do I make sure of this?
How would I support different versions of the same object ?
Sep 17, 2008 at 11:33 PM

Hi Ganesh,

I am not sure about the answers for all your questions, so I just try to answer what I know.

>A quick question, is the Import and Export atributes available in .net 2.0?
No. The ImportAttribute/ExportAttribute are newly defined in MEF.

>What if my classes are compiled into a separate dll? will MEF scan through all dlls in a bin folder to figure out which are the extensibility compatible dlls?
No. MEF will not automatically scan your bin folder unless you ask MEF to do so - using DirectoryPartCatalog to monitor a folder or explicitly loading an assembly using AttributedAssemblyPartCatalog.

>What if I want them in different folders, say for the sake of maintainability?
Use multiple DirectoryPartCatalogs (for each folder) and one AggregatingComposablePartCatalog.

>Why not have an option to tell an app which dlls to look for extensibility like a key value pair in a config file?
Your app can look at the config file and load only the assemblies specified. You can also use the filter of DirectoryPartCatalogs in some extent to filter out the dlls you do not want. But I think this is a good feedback that you hope to have some configuration capability on top of current catalog.

>How about at development time MEF will let me know what classes are available as extensions for a particular type?
We also have a tooling work item planned that is supposed to address requests like this.

>How would I support different versions of the same object ?
If different versions of parts are using different contracts, you can use contract adaptor to convert the old part using old contract to work with the new contract.

>Zhenlan's  Sep 11 at 10:23 PM  reply is also to be taken into account if you want to selectively instantiate a particular class out of a list. The question is how would a application developer know what are the loggers available in a library built by the "library developers"?
In my opinion, the app developer does not have to know. The app only looks for the loggers it expects from part libraries (the app won't work if finds nothing). It is library developers(part developers?)' responsibility to write loggers that can be used by the target app. Of course, if loggers are already there and an app developer wants to write a new app and leverage on the existing loggers - he/she already knows what are available.

Thanks for your feedback. Hope this answered some of your questions.

Thanks,
Zhenlan

Sep 18, 2008 at 3:10 PM
Edited Sep 18, 2008 at 3:13 PM
While trying to pick my brain, I came up with a picture of what I would expect the MEF should be. I could not paste the picture here so here is the link for MY MEF WISH

Would this be possible at all? 

PS the link is in Google DOCs. In case this is an issue , can some one tell me How I can add this to codeplex as a wishlist diagram?
Sep 19, 2008 at 3:53 AM
Very interesting! Is the blue box at the mid-top MEF? Could you please gave us some written description here of your diagram? (Not sure if it is a browser issue - some words in the picture are overlapped, so I cannot read).
Sep 19, 2008 at 3:32 PM
Thanks Zhenlan for having the patience to look at the ppt. No the blue box is a mixture of MEF and Extensible Object finder ( this needs clean up)

I have done an update /cleanup/enhance to the diagram. It is now a PNG image at My MEF

My view of MEF is to look at extensibility and usage of classes enterprise wide. This is how I envisage my architecture. I will take the now famous LOGGER as the Extensible Object.
There might be different flavors of loogers and each application in my Enterprise Biz App cluster (The Cloud) will use one / some or all of the available Loggers.

In order to get a Logger object a biz application will,

Send a message requesting for the logger through a "Conduit" to a "Extensible Object Finder" (EOFinder) (Step 1).
The EOFinder will look in its internal list (Step 2) find if there is an object available. If so , it will make a copy and send it to the biz application  (Step 9)
If there is none , it will send a message to the catalog (Steps 3 and 4) to get the location of the Assembly(dll).
The catalog will return  a value to identify the location of the assembly(Step 5)
The EOFinder then will send a message to load the Assembly after downloading it and the other necessary dlls(HOW does it know?) from its repository (Steps 6, 7 and 8).
A new object of the type will be instantiated and a copy of it will be sent back to the biz application(Step 9). The original will be placed in the internal list.

All suggestions and criticism are welcome.