Search code examples
c#mvvmprismmef

Understanding MEF System.Lazy<T,TMetaData>


I have been looking at the inner workings of the StockTrader RI for PRISM.

In this RI, MEF and a custom attribute system are used in combination to register views with regions as opposed to hooking up things to the RegionManager in the Module Initializer.

More specifically, there is a ViewExportAttribute which implements:

  1. MetaDataAttribute
  2. IViewRegionRegistration

The MetaDataAttribute and the "Attribute View" IViewRegionRegistration can be leveraged by System.Lazy<T,TMetaData> in AutoPopulateExportedViewsBehavior to achieve proper linking of regions and views.

In general the interplay between System.Lazy<T,TMetaData> and the actual metadata is elaborated here, more specifically the section "Using Strongly-typed Metadata".

To be clear, I understand the intent of Lazy and it clearly works. However, what I completely do not understand is where and how the link occurs between the metadata view supplied by the attribute (which is just an interface) and filling the TMetaData properties with the actual data supplied by the MetaDataAttribute.

To make my request even clearer, from the previously referenced example:

First, an interface is defined that can serve as a sort of template to pass certain metadata:

public interface IMessageSenderCapabilities
{
    MessageTransport Transport { get; }
    bool IsSecure { get; }
}

Next, A corresponding MetaDataAttribute is defined (which has the same properties as the previous interface)

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderAttribute : ExportAttribute
{
    public MessageSenderAttribute() : base(typeof(IMessageSender)) { }
    public MessageTransport Transport { get; set; }
    public bool IsSecure { get; set; }
}

The attribute can be used in an export, where actual values are set for the attribute properties:

[MessageSender(Transport=MessageTransport.Smtp, IsSecure=true)]
public class SecureEmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

Now finally, we can do some importing:

public class HttpServerHealthMonitor
{
    [ImportMany]
    public Lazy<IMessageSender, IMessageSenderCapabilities>[] Senders { get; set; }

    public void SendNotification()
    {
        foreach(var sender in Senders)
        {
            if (sender.Metadata.Transport == MessageTransport.Smtp && 
                sender.Metadata.IsSecure)
            {
                var messageSender = sender.Value;
                messageSender.Send("Server is fine");

                break;
            }
        }
    }
}

In this last step: sender.Metadata.Transport is evaluated on that very Lazy<>. Therefore, somewhere along the way, Lazy is made aware of the actual values of the metadata, not just the interface it gets passed. I want to understand how that happens, who or what is responsible for that. Even if it is just a very general flow.


Solution

  • After some more Reflector I think I can start to formulate an answer, although it turns out a lot of things are happening so this answer might evolve. I am writing it down hear for the benefit of learning this myself.

    1. MEFBootsrapper.Run()

      ...

    2. MEFBootstrapper.Container.GetExports(...) because CompositionContainer : ExportProvider, ... and ExportProvider defines public Lazy<T, TMetadataView> GetExport<T, TMetadataView>().

    3. Next private Lazy<T, TMetadataView> GetExportCore<T, TMetadataView>(string contractName)

    4. Next internal static Lazy<T, M> CreateStronglyTypedLazyOfTM<T, M>(Export export)

    5. In here, AttributedModelServices.GetMetadataView<M>(export.Metadata) where M is the type of the MetaDataView. Whereas export is itself of type System.ComponentModel.Composition.Primitives.Export and this has a field ExportDefenition of which an inherited AttributedExportDefenition exists.

    6. AttributedExportDefenition.MetaData whose getter contains this._member.TryExportMetadataForMember(out strs);

    7. TryExportMetadataForMember(...) finally has a check type.IsAttributeDefined<MetadataAttributeAttribute> to see if there is a MetadataAttribute applied such as for MessageSenderAttribute in the question.

    So this is more or less (very roughly) how we get to the actual metadata on the export and so probably with some more detours these exported metadata will also reach the Lazy although I am still to find out how that would work exactly.

    Any feedback would still be appreciated.