C# Corner

Managed Extensibility Framework Improvements in .NET 4.5

One of the most significant updates is the introduction of a convention-based extension model, in which you can configure a set of naming conventions to allow MEF parts to be easily picked up by your application.

The Microsoft .NET Framework 4.5 includes many updates to the Managed Extensibility Framework (MEF). Two of the most significant updates are the support for generic types and the introduction of a convention-based extension model. I'll cover the convention-based programming model available in MEF.

Up until the .NET Framework 4.5, the standard way of using MEF was through the Export and Import attributes. With the .NET Framework 4.5 you now have the option of configuring a set of naming conventions to allow MEF parts to be easily picked up by your application.

Creating an application is a great way to demonstrate how to use the MEF convention-based model. First, create a new C# .NET 4.5 Windows Presentation Foundation (WPF) app within Visual Studio 2012. Next, add references to the System.ComponentModel.Composition and System.ComponentModel.Composition.Registration assemblies, as seen in Figure 1.

[Click on image for larger view.] Figure 1. Adding MEF references for System.Composition.

Then add a project reference to System.Reflection.Context, as seen in Figure 2.

[Click on image for larger view.] Figure 2. Adding a reference to System.Reflection.Context.

Now, add the MEF parts that will be used by the WPF application. Create a new assembly called Business. Add a new folder to the project called Entities, then add a class to the Entities folder named Person. Person will be the entity class that will be validated by a custom MEF part. Create a Person class with an integer Id property as well as FirstName and LastName string properties:

namespace VSMMefWpfDemo.Business.Entities
{
  public class Person
  {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }
}

Next, add the IValidateEntity interface that will be implemented by a concrete ValidatePerson class. The IValidateEntity interface contains a single method called Validate that takes a given entity class and returns a Boolean:

namespace VSMMefWpfDemo.Business.Validator
{
  public interface IValidateEntity<T>
    where T: class
  {
    bool Validate(T entity);
  }
}

Now it's time to add the concrete ValidatePerson class that implements the IValidateEntity interface. The ValidatePerson class simply checks if the Person entity contains non-empty FirstName and LastName property values:

namespace VSMMefWpfDemo.Business.Validator
{
  public class ValidatePerson : IValidateEntity<Entities.Person>
  {
    public bool Validate(Entities.Person entity)
    {
      return !string.IsNullOrWhiteSpace(entity.FirstName) 
        && !string.IsNullOrWhiteSpace(entity.LastName);
    }
  }
}

Next, add the UI to the WPF portion of the application. Open the MainWindow.xaml file and copy in the XAML shown here within the root Grid element:

<StackPanel>
  <TextBlock>First Name</TextBlock>
  <TextBox Name="FirstName" Text="{Binding FirstName}"></TextBox>
  <TextBlock>Last Name</TextBlock>
  <TextBox Name="LastName" Text="{Binding LastName}"></TextBox>
  <Button Name="ValidateButton" Click="ValidateButton_Click">Validate</Button>
  <TextBlock Name="Status"></TextBlock>
</StackPanel>

Your MainWindow.xaml file should now resemble Listing 1.

Listing 1. The MainWindow.xaml file with XAML.

<Window x:Class="VSMMefWpfDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <Grid>
    <StackPanel>
      <TextBlock>First Name</TextBlock>
      <TextBox Name="FirstName" Text="{Binding FirstName}"></TextBox>
      <TextBlock>Last Name</TextBlock>
      <TextBox Name="LastName" Text="{Binding LastName}"></TextBox>
      <Button Name="ValidateButton" Click="ValidateButton_Click">Validate</Button>
      <TextBlock Name="Status"></TextBlock>
    </StackPanel>
  </Grid>
</Window>

Finally, implement the MEF convention-based rules for the application. The application will allow the user to edit and validate a Person record. The actual validation will be performed through the ValidatePerson class, which is loaded through MEF.

The first step is to add the following using statements to the MainWindow class:

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Registration;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using VSMMefWpfDemo.Business.Validator;
using VSMMefWpfDemo.Business.Entities;

Next, a CompositionContainer private member variable is added to the MainWindow class:

private CompositionContainer _container;

Then the PersonValidation property is added to the class, which will be loaded through MEF later:

public IValidateEntity<Person> PersonValidator  { get; set; }

Next, a private, read-only member variable is added to MainWindow to contain the UI data-bound Person entity:

private readonly Person _model = new Person();

In the MainWindow constructor the DataContext is set for the UI and the MEF ValidatePerson class is registered:

public MainWindow()
{
  InitializeComponent();
  DataContext = _model;
  RegisterMefParts();
}

Now it's time to implement the RegisterMefParts method. The main component of the convention-based model is the RegistrationBuilder class. The RegistrationBuilder class allows a set of convention-based rules to be defined for exporting and importing MEF parts. Within the RegisterMefParts method a new RegistrationBuilder object is created:

var conventions = new RegistrationBuilder();

The next step is to define a rule for a build-time, runtime or pattern-matched Type. (These three options are set through the ForType<>, ForType and ForTypesMatching methods, respectively.) In addition, you can specify that a rule is for an interface or base class through the ForTypesDerivedFrom and ForTypesDerivedFrom<> RegistrationBuilder methods. Once you have a type defined, the next step is to specify if you'd like to export or import the type through the PartBuilder API. The Export method on PartBuilder is used to export a type for use by MEF. For the sample application, a rule is set up for the IValidateEntity<Person> interface to export itself:

conventions.ForTypesDerivedFrom<IValidateEntity<Person>>()
                        .Export<IValidateEntity<Person>>(); 

Alternatively, you could set up a matching rule that would export any types defined in the Business.Validator namespace:

conventions.ForTypesMatching(x => x.Namespace.Contains("Business.Validator")).ExportInterfaces(x => x.IsPublic);

Next, the PersonValidator Type is set up as an imported property through the MEF convention model:

conventions.ForType<MainWindow>().ImportProperty(x => x.PersonValidator);

Now it's time to create the MEF catalog that contains the current executing assembly and the Business assembly:

Assembly businessAssembly = typeof (IValidateEntity<>).Assembly;
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new AssemblyCatalog(businessAssembly, conventions));

Now that the catalog is set up, the CompositionContainer can be created with the catalog:

_container = new CompositionContainer(catalog,
                                      CompositionOptions.DisableSilentRejection |
                                      CompositionOptions.IsThreadSafe);

The last step in the RegisterMefParts method is to call the SatisfyImportsOnce method on the _container to instantiate the PersonValidation property through MEF:

try
{
  _container.SatisfyImportsOnce(this, conventions);
}
catch (CompositionException ex)
{
  MessageBox.Show((ex.Message));
}

If an error occurs during the MEF part set up, I display a MessageBox with the exception message. Now it's time to implement the ValidateButton click event. When the Validate button is clicked, the Status TextBlock in the UI is updated to display "Person is Valid" if the Person entity passed validation, or "Person isn't valid," otherwise:

private void ValidateButton_Click(object sender, RoutedEventArgs e)
{
  Status.Text = PersonValidator.Validate(_model) ? "Person is valid." : "Person isn't valid.";
}

Your completed MainWindow class should now look like the code in Listing 2.

Listing 2. The completed MainWindow.xaml.cs.

using System.Windows;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Registration;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using VSMMefWpfDemo.Business.Validator;
using VSMMefWpfDemo.Business.Entities;


namespace VSMMefWpfDemo
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    private CompositionContainer _container;

    public IValidateEntity<Person> PersonValidator  { get; set; }

    private readonly Person _model = new Person();

    public MainWindow()
    {
      InitializeComponent();
      DataContext = _model;
      RegisterMefParts();
    }

    private void RegisterMefParts()
    {
      var conventions = new RegistrationBuilder();
      conventions.ForTypesDerivedFrom<IValidateEntity<Person>>()
                              .Export<IValidateEntity<Person>>();

     // conventions.ForTypesMatching(x => x.Namespace.Contains("Business.Validator")).ExportInterfaces(x => x.IsPublic);

      conventions.ForType<MainWindow>().ImportProperty(x => x.PersonValidator);


      Assembly businessAssembly = typeof (IValidateEntity<>).Assembly;
      var catalog = new AggregateCatalog();
      catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
      catalog.Catalogs.Add(new AssemblyCatalog(businessAssembly, conventions));

      _container = new CompositionContainer(catalog,
                                            CompositionOptions.DisableSilentRejection |
                                            CompositionOptions.IsThreadSafe);

      try
      {
        _container.SatisfyImportsOnce(this, conventions);
      }
      catch (CompositionException ex)
      {
        MessageBox.Show((ex.Message));
      }
        }

    private void ValidateButton_Click(object sender, RoutedEventArgs e)
    {
      Status.Text = PersonValidator.Validate(_model) ? "Person is valid." : "Person isn't valid.";
    }
  }
}

The application is now complete. You should be able to validate an invalid Person (Figure 3) or a valid Person entity (Figure 4).

[Click on image for larger view.] Figure 3. Validating an invalid Person entity.
[Click on image for larger view.] Figure 4. Validating a valid Person entity.

As you can see, the new MEF convention-based programming model allows for an attribute-less extensibility solution. I recommend using the convention-based model for internal extensibility within an application. When an application is to be extended externally, the attribute-based model is better suited, as it's more explicit.

About the Author

Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube