Annotated Data Contracts and the Content Enricher pattern

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

Recently I worked with a team to design a Silverlight client that consumes some WCF services. Most of the services talk in the Silverlight space is about WCF/RIA data services and how we can drag and drop our way to SOA. That wasn't a suitable approach for this project.

When designing the services, a trend that we found was that a single service might return a message containing two categories of data:

  • The real data we wanted
  • Metadata, related to the real data, that was needed purely for client purposes

Examples of real data included the name and salutation of a patient, or the results of an investigation.

Metadata included whether the requesting user has seen that record before, whether the requesting user would have permission to view the details of the full record or just the summary, whether the result was safe to be cached, and so-on. It was information about the data we were receiving, distinct from the data itself - much like the way a HTTP response includes headers and content.

Normally, this kind of data might appear as headers in the message contract. But since the metadata was related to individual elements within the response, and not the response as a whole, this wasn't much of an option.

Annotated Objects

The eventual design was to use a system of annotations. The idea was that any DTO in our message contracts could be "tagged" with this additional metadata.

The annotated object model

The DTO's don't know what the annotations are for, how they got them, or why they are carrying them. But they faithfully carry them across the wire to the client, ready to be consumed. It is much like the attached dependency property design in WPF and Silverlight.

Applying Annotations

The metadata is often sourced from a different location than the real data. Patient records might come from our health system, but information about whether the a doctor has seen this patient might come from an auditing system. We might source patient data using many different queries, but the way we apply auditing data is much the same.

Behind our services, the intention is to use a Content Enricher design. We can write different services to fetch patient information in many different shapes as needed, and the content enricher will be automatically applied at the service boundary to apply the annotations.

The content enricher would need to visit every object in the DTO object graph, and add an annotation. Here's how such a content enricher might be implemented:

public class PreviouslyViewedPatientsContentEnricher : IContentEnricher
{
    public object Enrich(object message)
    {
        var patients = new List<PatientReference>();

        // Find all patient references in the object graph
        var messageVisitor = new MessageVisitor();
        messageVisitor.OnEncountering<PatientReference>(p => patients.Add(p));
        messageVisitor.Visit(message);

        // Enrich the patients by tagging them with a previously viewed annotation
        foreach (var patient in patients)
        {
            var viewed = // Has this patient been viewed before?
            patient.Annotations.Add(new PreviouslyViewed(viewed));
        }

        return message;
    }
}

A pipeline of content enrichers could be associated with each service. This content enricher pipeline could be applied in a few different ways:

  • Using Policy Injection or any other form of aspect oriented programming
  • Using a WCF extension point, such as IDispatchMessageFormatter.

This decouples the acquisition of the metadata with the acquisition of the real data.

Consuming Annotations

From the client, we can use the Annotations property on any Annotated object to check if it has an annotation we are interested in. Our Silverlight application might use some Blend Behaviors to check for a specific annotation and apply a visual state group. The styles and templates for a UI element, such as a Hyperlink, could then make use of those visual state groups.

The behavior below checks for the PreviouslyViewed annotation:

public class SeenBeforeBehavior : Behavior<DependencyObject>
{
    public static readonly DependencyProperty SeenBeforeProperty = DependencyProperty.RegisterAttached("SeenBefore", typeof(bool), typeof(SeenBeforeBehavior), new PropertyMetadata(false));

    public SeenBeforeBehavior()
    {
    }

    protected override void OnAttached()
    {
        base.OnAttached();

        var associatedControl = (Control)AssociatedObject;
        var annotated = associatedControl.DataContext as Annotated;
        if (annotated == null)
            return;

        var previouslyViewed = annotated.Annotations.Get<PreviouslyViewed>();
        if (previouslyViewed == null)
            return;

        associatedControl.SetValue(SeenBeforeProperty, previouslyViewed.SeenByThisUser);
    }
}

From WPF, we can then use a simple style trigger to check for the attached property and style our controls accordingly. Or from Silverlight, I'm sure we could do it with the Visual State Manager with enough lines of XAML :)

Summary

In this post I outlined a design for associating metadata with DTO's, using the content enricher pattern on the server to attach them, and using Blend behaviors on the client to consume them. I'm interested in what you think of the above design and any holes or issues you can see.