Dynamic event handlers for Xaml-only Silverlight content (XamlReader, “Ramora” pattern and MEF)


The complete source code for the article can be found here.

 

Since developers started using XamlReader and XamlWriter in WPF the question towards events and event handlers seem to be one of the most hottest both for WPF and Silverlight. In this article I’m going to show you one of the possible ways to provide event handlers for Silverlight controls that are instantiated from a Xaml dynamically by means of XamlReader. I will be using MEF Preview 9 to glue parts together in my sample.

Imagine the following scenario:

 

Some “business form” is designed within some thick/thin client and deployed to a server-side repository (database) as a string representation of xaml. Later on a Silverlight client loads this form data, turns into live UI layout and presents to a user. There is a set of logic should be involved during the “business form” life cycle taking into account such events as “Form Load”, “Submit Button Click”, “Cancel Button Click” etc. These events should be wired by the owner form during string-to-content conversion.

Hint: When a “business” form is being converted from its string-based representation to a live Silverlight content the types for resulting object graph are taken from the active application domain. This means that you can declare any type of control if you are sure the assembly containing this control is loaded during runtime. For example if your shell references “Silverlight Toolkit”, the XamlReader will allow you creating Charts from string-based content.

I’ll be using the awesome “Ramora” pattern I’ve been a greatest fun of since it was suggested by Ben Constable here. The reason is that XamlReader provides support for Attached Properties for parsed content. This means that every Attached Property can serve as a some sort of entry point for application specific logic by means of “Ramora” pattern.

 

You may want creating a separate “Silverlight Class Library” to hold contract classes and services that will be loaded during runtime and could be accessible by XamlReader when parsing strings. In my sample this library will be called “XamlOnlyUtils” but you can call it whatever you want.

 

Customizing Export attribute to suit our needs

Latest MEF previews unseal “ExportAttribute” in order to support highly customizable solutions and allow hiding MEF from the “end-developer” levels. For more details please refer to “Exports and Metadata” section in MEF documentation.

 

I’ve created an application specific attribute that will allow you exporting event handlers:

“XamlEventHandlerAttribute.cs”

using System;
using System.ComponentModel.Composition;
using System.Windows;

namespace XamlOnlyUtils
{
  [MetadataAttribute]
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  public sealed class XamlEventHandlerAttribute : ExportAttribute
  {
    public string Name { get; private set; }

    public XamlEventHandlerAttribute(string name)
      : base("XamlEventHandlers", typeof(Action<DependencyObject>))
    {
      this.Name = name;
    }
  }
}

The attribute above will hold the “Name” of event handler and will be scoped by the “XamlEventHandlers” contract addressing "”Action<DependencyObject>” handlers. This is a minimalistic implementation and you can easily modify it to cover complex scenarios later on.

As a result you will be able creating a handler that looks similar to that below:

[XamlEventHandler("CustomOnLoad")]
   public void CustomOnLoadHandler(DependencyObject sender)
   {
     MessageBox.Show("Loosely bound event handler fired!");
   }

 

Providing strongly-typed metadata

 

To simplify handlers discovery and invokation I’ve created a simple interface to expose “Name” property of each imported action:

namespace XamlOnlyUtils
{
  public interface IXamlEventHandlerMetadata
  {
    string Name { get; }
  }
}

If you want getting more details towards strongly-typed metadata and its usage please refer to “Exports and Metadata” section in MEF documentation.

 

Creating and consuming a simple event

Now we need exposing “Ramora” pattern to instrument a simple “Loaded” event. Let’s assume we want handling the “FrameworkElement.Loaded” event in a dynamic way.

Create a new Dependency object in the “XamlOnlyUtils” library and call it “XamlEventServices”:

public class XamlEventServices : DependencyObject

This class will store all attached properties required to accomplish the task. I’ve inherited it from a DependencyObject in order to have access to Dispatcher in future.

Now let’s define a simple “Loaded” event handler. It will be an attached property holding the name of the handler:

public static readonly DependencyProperty OnLoadedProperty =
     DependencyProperty.RegisterAttached(
       "OnLoaded",
       typeof(string),
       typeof(XamlEventServices),
       new PropertyMetadata(null, OnLoadedPropertyChanged));

Exposing getter and setter in the common way:

public static string GetOnLoaded(DependencyObject target)
    {
      return (string)target.GetValue(OnLoadedProperty);
    }

    public static void SetOnLoaded(DependencyObject target, string value)
    {
      target.SetValue(OnLoadedProperty, value);
    }

As you might have noticed I’m declaring a “property change” handler called “OnLoadedPropertyChanged” for attached property. This is where the “Ramora” implementation begins. The idea is to wire additional logic as soon as “OnLoaded” attached property is defined for an object. As XamlReader takes care of parsing and assigning attached properties we can be sure that event handler will be fired during parsing phase and we’ll get the expected entry point.

Here’s how the implementation of “OnLoadedPropertyChanged” handler looks like on my side:

private static void OnLoadedPropertyChanged(
      DependencyObject sender, 
      DependencyPropertyChangedEventArgs e)
    {
      FrameworkElement fe = sender as FrameworkElement;
      if (fe == null) return;

      bool wasWired = (e.OldValue != null);
      bool needsWiring = (e.NewValue != null);

      if (wasWired) fe.Loaded -= OnLoadedHandler;
      if (needsWiring) fe.Loaded += OnLoadedHandler;
    }

This method wires “FrameworkElement.Loaded” event upon every attached property value change and covers 2 scenarios at once. It can either wire a new event handler if the property has changed for the first time or unwire the previously assigned handler if we want removing it by passing null value.

 

Here’s how my “FrameworkElement.Loaded” handler looks like:

static void OnLoadedHandler(object sender, RoutedEventArgs e)
   {
     DependencyObject dobj = sender as DependencyObject;
     if (dobj == null) return;

     string handlerName = GetOnLoaded(dobj);
     if (string.IsNullOrEmpty(handlerName)) return;

     var resolver = new EventHandlersResolver();
     CompositionInitializer.SatisfyImports(resolver);

     var handlers = resolver.EventHandlers
       .Where(row => row.Metadata.Name.Equals(
         handlerName, StringComparison.InvariantCultureIgnoreCase));

     foreach (var handler in handlers)
       handler.Value(dobj);
   }

Couple of details towards the code above: the first thing I’m trying to do is to get the name of the handler that was defined via the attached property. After that I create a "resolver” class that will hold all the external actions marked with “XamlEventHandlerAttribute”. After satisfying all imports for this resolver (simply getting all the action exports) I enumerate the result to find the actions matching the name defined by attached property and invoke every found action. This means that you can have multiple actions being fired for a single event.

 

Resolving exported actions

 

Attached properties is a world of statics. The property holder class (in my case it is “XamlEventServices”) is not instantiated each time a dependency property is used so it is quite difficult satisfying imports on it. I strongly recommend following MEF team suggestions not to try wiring static classes with MEF bus as It brings out additional implications in the future. Instead I’m using the “resolver” class and satisfy imports on the fly. Here’s how my simple resolver looks like:

 

using System;
using System.ComponentModel.Composition;
using System.Windows;

namespace XamlOnlyUtils
{
  public class EventHandlersResolver
  {
    [ImportMany("XamlEventHandlers", typeof(Action<DependencyObject>))]
    public Lazy<Action<DependencyObject>, IXamlEventHandlerMetadata>[] EventHandlers { get; set; }
  }    
}

It contains a single property collecting all “Action<DependencyObject>” exports scoped by “XamlEventHandlers” contract. You don’t need enabling recomposition here because imports will be collected dynamically when “FrameworkElement.Loaded” event is fired.

 

Testing loose handlers on “live” control

 

To ensure the loose event handler binding actually works you can do the following steps. Create a new Silverlight Application and reference the “contract” assembly “XamlOnlyUtils” (you can add reference to a project if you create application in the same solution).

 

Go to the code behind of the “MainForm.xaml” (“MainForm.xaml.cs”) and declare the following method:

[XamlEventHandler("CustomOnLoad")]
   public void CustomOnLoadHandler(DependencyObject sender)
   {
     MessageBox.Show("Loosely bound event handler fired!");
   }

You’ll be showing a dummy message box to ensure the infrastructure is working. Now return back to the xaml side and assign the “OnLoaded” attached property directly for the main form like on the screenshot below:

 

image

 

The workflow you expect to be executed is as follows: when the “OnLoaded” attached property is assigned, our “XamlEventServices” will attach a custom handler to control’s native “Loaded” event. As soon as control is loaded and a corresponding event is fired, “XamlEventServices” will resolve the imports for handler actions, find any action named as “CustomOnLoad” and execute them.

 

Configuring MEF for application

 

In order the sample above to work as expected you will need to actually enable MEF for the application. As my “MainForm.xaml.cs” contains some actions that need to be exported, I’ve decided to export the entire form to be able satisfying all the imports easily. For this purpose I’m marking “MainForm” with “ExportAttribute”:

 

[Export]
public partial class MainPage : UserControl
{

 

After that you will need opening the “App.xaml.cs” to configure composition host and satisfy imports (providing all the corresponding exports we are interested in). Here are the changes I’ve made to accomplish that:

 

1. Importing form into the application class:

 

public partial class App : Application
 {
   [Import]
   public MainPage MainPage { get; set; }

 

2. Rewiring root visual to the form being imported:

 

private void Application_Startup(object sender, StartupEventArgs e)
   {      
     this.RootVisual = this.MainPage;
   }

 

3. Finally providing a quick configuration of MEF including both assemblies I’ve been working with:

 

image

 

Note: for more details towards MEF hosting please refer to “Hosting MEF in an application” section of MEF documentation.

 

I assume that both projects I’ve been demonstrating already contain references to MEF “Composition” and “Initialization” for Silverlight assemblies:

 

image

 

When running the application you should see the Message Box displayed by “CustomOnLoadHandler” defined in the code behind of the main form:

 

image

 

This means that the loose infrastructure is working as expected. The method was successfully exported, discovered and fired.

 

 

Testing XamlReader and string-based xaml content

 

Now we need turning some string-based content into the live object graph and ensure the events can be wired during XamlReader parsing phase. I’ve changed the main form to contain a test Button that will start string-to-xaml conversion and a placeholder to put the resulting UI into:

 

image

 

Here’s the sample content I will be trying to process:

 

image

 

As I’ve mentioned at the beginning of the article, I can freely rely on the attached properties that are present within the application domain during runtime. I’ve imported a namespace from the “contract” library and configured “OnLoaded” attached property to point to the “CustomOnLoad” action that is expected to be defined somewhere in the hosting application.

 

Finally I provide some logic for “btnLoadXaml” Button Click handler:

 

private void btnLoadXaml_Click(object sender, RoutedEventArgs e)
  {
    FrameworkElement fe = XamlReader.Load(XamlContent) as FrameworkElement;
    if (fe != null)
      this.xamlContainer.Content = fe;
  }

 

In the code above I turn “XamlContent” string into a fully-fledged object graph (with all the attached properties processed) and assign the result to the Content property of my “xamlContainer” placeholder. As soon as It is done, the UI will be loaded and a corresponding “Loaded” event will be fired so causing the “OnCustomLoad” action be discovered and executed.

 

As soon as you click the “Load Xaml” button you should see the following result:

 

image

 

This article dwells on the very basic scenarios to demonstrate the things working indeed. Feel free extending it to support more complex scenarios.

 

The complete source code for the article can be found here.

Advertisements

3 thoughts on “Dynamic event handlers for Xaml-only Silverlight content (XamlReader, “Ramora” pattern and MEF)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s