Sharing Functionality Between Designers (WF4 EditingContext Intro Part 2)

This part 2 of my 6 part series on the EditingContext.

 

Setup

We will need a custom activity, EmptyOne and designer called InteractWithServiceDesigner. 

 using System.Activities;
using System.ComponentModel;

namespace blogEditingContext
{
    [Designer(typeof(InteractWithServicesDesigner))]
    public sealed class EmptyOne : CodeActivity
    {
        // Define an activity input argument of type string
        public InArgument<string> Text { get; set; }

        // If your activity returns a value, derive from CodeActivity<TResult>
        // and return the value from the Execute method.
        protected override void Execute(CodeActivityContext context)
        {
            // Obtain the runtime value of the Text input argument
            string text = context.GetValue(this.Text);
        }
    }
}

What We Will See

The designers for Foo will leverage a new service in order to display a list of database tables.  We will also need to publish this service to the editing context, and handle the fact that we don’t know who might publish it (or when it might be published).  Note that in VS, there is no way to inject services except by having an activity designer do it.   In a rehosted app, the hosting application could publish additional services (see part 4) that the activities can consume.  In this case though, we will use the activity designer as our hook.

Publishing a Service

Let’s look at the designer for Foo (as Foo is our generic, and relatively boring activity).

 <sap:ActivityDesigner x:Class="blogEditingContext.InteractWithServicesDesigner"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
    <StackPanel>
        <ListBox Height="100" Name="listBox1" Width="120" />
        <Button Name="button1" Click="Button_Click">Publish Service</Button>
    </StackPanel>
</sap:ActivityDesigner>

Not much to this, except a drop down list that is currently unbound (but a name is provided).  Also note that there is a button that says to “publish the service”.  Let’s first look at the code for the button click

 private void Button_Click(object sender, RoutedEventArgs e)
{
    if (!this.Context.Services.Contains<ISampleService>())
    {
        this.Context.Services.Publish<ISampleService>(new SampleServiceImpl());
    }
}

What are we doing here?  We first check if this service is already published using the Contains method.  We can do this because ServiceManager implements IEnumerable<Type>.

[update, finishing this sentence.] One could also consume the service using  GetService<TResult>.  You may also note that there is a GetRequiredService<T>.  This is a call that we know won’t return null, as the services we are requesting must be there for the designer to work.  Rather than returning null, this will throw an exception. Within the designer, we generally think of one service as required:

Let’s look at the definition of the service.  Here you can see that we are using both an interface and then providing an implementation of that interface.  You could just as easily use an abstract class, or even a concrete class, there is no constraint on the service type.

 using System.Collections.Generic;

namespace blogEditingContext
{
    public interface ISampleService
    {
        IEnumerable<string> GetDropdownValues(string DisplayName);
    }

    public class SampleServiceImpl : ISampleService
    {
        public IEnumerable<string> GetDropdownValues(string DisplayName)
        {
            return new string[]  { 
                DisplayName + " Foo", 
                DisplayName + " Bar",
                "Baz " + DisplayName
            } ;
        }
    }

}

If there is not a service present, we will publish an instance of one.  This becomes the singleton instance for any other designer that may request it.  Right now, we have a designer that can safely publish a service.  Let’s look at consuming one

Consuming a Service

Let’s look at some code to consume the service.  There are two parts to this.  One is simply consuming it, which we already saw above in discussing GetService and GetRequiredService .  The second is hooking into the notification system to let us know when a service is made available.  In this case, it’s a little contrived, as the service isn’t published until the button click, but it’s good practice to use the subscription mechanism as we make no guarantees on ordering, or timing of service availability.

Subscribing to Service

Here, using the Subscribe<TServiceType> method, we wait for the service to be available.  The documentation summarizes this method nicely:

Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately.

In the OnModelItemChanged method, we will subscribe and hook a callback.  The callback’s signature is as follows:

 public delegate void SubscribeServiceCallback<TServiceType>(
    TServiceType serviceInstance
)

As you can see, in this callback, the service instance is provided, so we can query it directly. You may ask, “why not in Intialize?”  well, there are no guarentees that the editing context will be available at that point. We could either subscribe to context being made available, or just use ModelItemChanged:

 protected override void OnModelItemChanged(object newItem)
{
    if (!subscribed)
    {
        this.Context.Services.Subscribe<ISampleService>(
            servInstance =>
            {
                listBox1.ItemsSource = servInstance.GetDropdownValues(this.ModelItem.Properties["DisplayName"].ComputedValue.ToString());
                button1.IsEnabled = false;
            }
            );
        subscribed = true; 
    }
}

This wraps a basic introduction to the ServiceManager type and how to leverage it effectively to share functionality in designers.

Let’s look at a before and after shot in the designer:

Before & After

before after

 

 

What about Items?

Items follow generally the same Get, Subscribe, and publish pattern, but rather than publish, there is a SetValue method.  If you have “just data” that you would like to share between designers (or between the host and the designer) an Item is the way to go about that.  The most commonly used item we’ve seen customers use is the Selection item in order to be able to get or set the currently selected model item.

That’s our tour of basic publish and subscribe with Services and Items.

 

 

[updated 12/22/2009 @ 10:23 am to finish an unclear sentence about GetService<>]

[updated 12/22/2009 @ 8:50pm : Link to download sample code is here]

Attachment(s): blogEditingContext.zip