Filtering Catalogs

When using child containers it may be important to filter catalogs based on some specific criteria. For example, it is common to filter based on part's creation policy. The following code snippet demonstrates how to set up this particular approach:

var catalog = new AssemblyCatalog(typeof(Program).Assembly);
var parent = new CompositionContainer(catalog);

var filteredCat = new FilteredCatalog(catalog,
    def => def.Metadata.ContainsKey(CompositionConstants.PartCreationPolicyMetadataName) &&
    ((CreationPolicy)def.Metadata[CompositionConstants.PartCreationPolicyMetadataName]) == CreationPolicy.NonShared);
var child = new CompositionContainer(filteredCat, parent);

var root = child.GetExportedObject<Root>();
child.Dispose();
Dim catalog = New AssemblyCatalog(GetType(Program).Assembly) 
Dim parent = New CompositionContainer(catalog) 

Dim filteredCat = New DirectoryCatalog(catalog, Sub(def) def.Metadata.ContainsKey(CompositionConstants.PartCreationPolicyMetadataName) AndAlso (CType(def.Metadata(CompositionConstants.PartCreationPolicyMetadataName), CreationPolicy)) = CreationPolicy.NonShared) 
Dim child = New CompositionContainer(filteredCat, parent) 

Dim root = child.GetExportedObject(Of Root)() 
child.Dispose()

If CreationPolicy is not enough as a criteria to select parts you may want to use the [System.ComponentModel.Composition.PartMetadataAttribute] instead. It allows you to attach metadata to the part so you can use it to construct a filtering expression. For example, the following is a class that with the attribute applied:

[PartMetadata("scope", "webrequest"), Export]
public class HomeController : Controller
{
} 
<PartMetadata("scope", "webrequest"), Export()>
Public Class HomeController
    Inherits Controller
End Class

This allows you to create a child container with parts that should be scoped to a (logical) web request. Note that it is up to you to define a scope boundary, in other words, MEF doesn't know what a "webrequest" is, so you have to create some infrastructure code to create/dispose containers per web request.

var catalog = new AssemblyCatalog(typeof(Program).Assembly);
var parent = new CompositionContainer(catalog);

var filteredCat = new FilteredCatalog(catalog,
    def => def.Metadata.ContainsKey("scope") &&
    def.Metadata["scope"].ToString() == "webrequest");
var perRequest = new CompositionContainer(filteredCat, parent);

var controller = perRequest.GetExportedObject<HomeController>();
perRequest.Dispose();
Dim catalog = New AssemblyCatalog(GetType(Program).Assembly) 
Dim parent = New CompositionContainer(catalog) 

Dim filteredCat = New FilteredCatalog(catalog, Function(def) def.Metadata.ContainsKey("scope") AndAlso def.Metadata("scope").ToString() = "webrequest")
Dim perRequest = New CompositionContainer(filteredCat, parent) 

Dim controller = perRequest.GetExportedObject(Of HomeController)() 
perRequest.Dispose()

Note that we do not provide a FilteredCatalog class. A simple implementation that illustrates what would take to build one follows:

using System;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Linq.Expressions;

public class FilteredCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
{
    private readonly ComposablePartCatalog _inner;
    private readonly INotifyComposablePartCatalogChanged _innerNotifyChange;
    private readonly IQueryable<ComposablePartDefinition> _partsQuery;

    public FilteredCatalog(ComposablePartCatalog inner,
                           Expression<Func<ComposablePartDefinition, bool>> expression)
    {
        _inner = inner;
        _innerNotifyChange = inner as INotifyComposablePartCatalogChanged;
        _partsQuery = inner.Parts.Where(expression);
    }

    public override IQueryable<ComposablePartDefinition> Parts
    {
        get
        {
            return _partsQuery;
        }
    }

    public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed
    {
        add
        {
            if (_innerNotifyChange != null)
                _innerNotifyChange.Changed += value;
        }
        remove
        {
            if (_innerNotifyChange != null)
                _innerNotifyChange.Changed -= value;
        }
    }

    public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing
    {
        add
        {
            if (_innerNotifyChange != null)
                _innerNotifyChange.Changing += value;
        }
        remove
        {
            if (_innerNotifyChange != null)
                _innerNotifyChange.Changing -= value;
        }
    }
}
Imports System
Imports System.ComponentModel.Composition.Primitives
Imports System.ComponentModel.Composition.Hosting
Imports System.Linq
Imports System.Linq.Expressions

Public Class FilteredCatalog
    Inherits ComposablePartCatalog
    Implements INotifyComposablePartCatalogChanged
    Private ReadOnly _inner As ComposablePartCatalog
    Private ReadOnly _innerNotifyChange As INotifyComposablePartCatalogChanged
    Private ReadOnly _partsQuery As IQueryable(Of ComposablePartDefinition) 

    Public Sub New(ByVal inner As ComposablePartCatalog, ByVal expression As Expression(Of Func(Of ComposablePartDefinition, Boolean))) 
        _inner = inner
        _innerNotifyChange = TryCast(inner, INotifyComposablePartCatalogChanged) 
        _partsQuery = inner.Parts.Where(expression) 
    End Sub

    Public Overrides ReadOnly Property Parts() As IQueryable(Of ComposablePartDefinition) 
        Get
            Return _partsQuery
        End Get
    End Property

    Public Custom Event Changed As EventHandler(Of ComposablePartCatalogChangeEventArgs) Implements INotifyComposablePartCatalogChanged.Changed 

        AddHandler(ByVal value As EventHandler(Of ComposablePartCatalogChangeEventArgs)) 
            If _innerNotifyChange IsNot Nothing Then
                AddHandler _innerNotifyChange.Changed, value
            End If
        End AddHandler
        RemoveHandler(ByVal value As EventHandler(Of ComposablePartCatalogChangeEventArgs)) 
            If _innerNotifyChange IsNot Nothing Then
                RemoveHandler _innerNotifyChange.Changed, value
            End If
        End RemoveHandler
        RaiseEvent(ByVal sender As System.Object, ByVal e As ComposablePartCatalogChangeEventArgs) 

        End RaiseEvent
    End Event

    Public Custom Event Changing As EventHandler(Of ComposablePartCatalogChangeEventArgs) Implements INotifyComposablePartCatalogChanged.Changing
        AddHandler(ByVal value As EventHandler(Of ComposablePartCatalogChangeEventArgs)) 

            If _innerNotifyChange IsNot Nothing Then
                AddHandler _innerNotifyChange.Changing, value
            End If
        End AddHandler
        RemoveHandler(ByVal value As EventHandler(Of ComposablePartCatalogChangeEventArgs)) 
            If _innerNotifyChange IsNot Nothing Then
                RemoveHandler _innerNotifyChange.Changing, value
            End If
        End RemoveHandler
        RaiseEvent(ByVal sender As System.Object, ByVal e As ComposablePartCatalogChangeEventArgs) 

        End RaiseEvent
    End Event
End Class

Last edited Aug 9, 2010 at 6:27 PM by haveriss, version 6

Comments

shaneh20 Mar 4, 2011 at 12:34 AM 
Shouldn't "Dim filteredCat = New DirectoryCatalog" be "Dim filteredCat = New FilteredCatalog"?

kbrowns Aug 6, 2010 at 12:04 PM 
Yeah looks like a renaming oversight... I don't see GetExportedObject<T>() - looks like that was renamed to GetExportedValue<T>()

dcstraw Jul 15, 2010 at 11:54 PM 
I came up with a solution - I have one container that contains only the 'global' parts, and then all other containers (the main one and all child containers) each filter out the global parts and include the global part container as an ExportProvider.

dcstraw Jul 15, 2010 at 10:58 PM 
Is there any way to filter the parent container to only expose certain shared instances to the child container? What I tried was to filter certain attributed parts out of the child and then include the parent as an ExportProvider, but then all other shared parts show up twice in my child container.

tilovell Apr 27, 2010 at 10:35 PM 
Where is the strongly typed PartMetadataAttribute functionality?? Oh no, seems like it's not there, PartMetadataAttribute is sealed.... :-(

laedit Apr 24, 2010 at 7:24 PM 
The method GetExportedObject has been replaced by GetExportedValue, no ?

gblock Apr 9, 2010 at 3:19 AM 
@wcoenen It's not enough. You need request-level containers in order to avoid threading contention issues as well as to completely reclaim resources when the request dies.

wcoenen Jan 22, 2010 at 11:02 AM 
I'm not sure you really need "to create some infrastructure code to create/dispose containers per web request". Couldn't you just create the object representing the scope with PartCreator<T>, and then dispose the PartLifetimeContext<T> when the scope needs to be cleaned up?

mcfilip Dec 14, 2009 at 9:02 AM 
Why FilteredCatalog is not part of MEF? Are there any chances to become?

weshaggard Oct 27, 2009 at 11:08 PM 
I've updated the FilteredCatalog to reflect the current (preview 8) code. Also we don't need to unhook any events in the dispose because we aren't explicitly hooking the change event any longer.

fa3den Oct 27, 2009 at 2:38 PM 
The inner catalog may not need to be disposed, but the Change event needs to be unwired.

KathleenDollard Sep 9, 2009 at 11:15 PM 
There are a few other differences with the current code base. Plan a little extra time for figuring this out until it gets updated.

KathleenDollard Sep 9, 2009 at 8:23 PM 
Apparently INotifyComposablePartCatalogChanged moved from ...Primitives to ...Hosting along the way and this sample not updated. If you have trouble, import System.ComponentModel.Composition.Hosting.

haveriss Aug 24, 2009 at 7:54 PM 
Not really. It does not take its ownership. Usually you will have a global catalog and say, filtered catalogs being created in shorter lifetimes (like per web request). You certainly dont want those disposing the global/parent catalog.

dcazzulino Aug 21, 2009 at 3:34 PM 
Shouldn't the FilteredCatalog dispose the inner catalog on Dispose?