Daniel Vaughan

.NET Adventures

ViewModel Active Awareness in a Prism Based Application

clock February 5, 2010 15:13 by author Daniel Vaughan

Yesterday, while chatting with the highly talented Jeremiah Morrill and other WPF Disciples about some MVVM subtleties (for the full post see here), Jeremiah briefly touched on the topic of providing ViewModels with the awareness of being active or inactive within a Prism based application. I wanted to explore this further, and decided to integrate this functionality into Calcium. What I provide here isn't rocket science, and merely serves to illustrate one of indeed many design approaches that could be applied to accomplish the same thing.

For this I had two goals. The first, to provide the capability without coupling the ViewModel to the View. That is, without requiring the ViewModel to have a reference to the View. The second, to not depend on a base class for the functionality; forever tying the developer to my base class implementation (ViewModelBase). So, indeed, I chose an interface based approach. To be Prism-esque I have adapted Jeremiah's approach which was to implement Prism's IActiveAware interface on my base view class. I then feed an intermediary object to the ViewModel via an interface named IViewAware. The intermediary object is an instance of ActiveAwareUIElementAdapter. This class is used to provide a UIElement instance with Prisms IActiveAware interface. It does so by monitoring its Got and LostFocus events.

 

ActiveAwareUIElementAdapter

 

/// <summary>
/// Wraps a <see cref="UIElement"/> to provide an <see cref="IActiveAware"/>
/// implementation based on its focus state.
/// </summary>
public class ActiveAwareUIElementAdapter : IActiveAware
{
    bool active;

    public ActiveAwareUIElementAdapter(UIElement uiElement)
    {
        ArgumentValidator.AssertNotNull(uiElement, "uiElement");
        uiElement.GotFocus += OnGotFocus;
        uiElement.LostFocus += OnLostFocus;
    }

    void OnLostFocus(object sender, RoutedEventArgs e)
    {
        IsActive = false;
    }

    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        IsActive = true;
    }

    public bool IsActive
    {
        get
        {
            return active;
        }
        set
        {
            if (active != value)
            {
                active = value;
            }
            OnIsActiveChanged(EventArgs.Empty);
        }
    }

    #region event IsActiveChanged

    event EventHandler isActiveChanged;

    public event EventHandler IsActiveChanged
    {
        add
        {
            isActiveChanged += value;
        }
        remove
        {
            isActiveChanged -= value;
        }
    }

    protected void OnIsActiveChanged(EventArgs e)
    {
        if (isActiveChanged != null)
        {
            isActiveChanged(this, e);
        }
    }

    #endregion
}

We then use this class within any IView UIElement implementation, but in particular the base ViewControl class. We instantiate the ActiveAwareUIElement adapter within the view's constructor, and pass it the instance of the view itself. The ActiveAwareUIElement then simply subscribes to the GotFocus and LostFocus events of the view, which is of course a UIElement.

 

ViewControl Implementation

 

/// <summary>
/// The base class for <see cref="IView"/>s.
/// </summary>
public class ViewControl : UserControl, IView, IActiveAware /* (not abstract for Blendability) */
{
    #region ViewModel Dependency Property

    public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
        "ViewModel", typeof(IViewModel), typeof(ViewControl), new PropertyMetadata(null, OnViewModelChanged));

    static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var viewControl = (ViewControl)d;
        viewControl.SetViewAwareAssociations((IViewModel)e.OldValue, (IViewModel)e.NewValue);
    }

    [Description("The view model for this view.")]
#if !SILVERLIGHT
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
#endif
    public IViewModel ViewModel
    {
        get
        {
            return (IViewModel)GetValue(ViewModelProperty);
        }
        set
        {
            SetValue(ViewModelProperty, value);
        }
    }

    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="ViewControl"/> class.
    /// </summary>
    public ViewControl()
    {
        Loaded += OnLoaded;
        activeAwareUIElementAdapter = new ActiveAwareUIElementAdapter(this); 
    }

    bool alreadyLoaded;

    void OnLoaded(object sender, RoutedEventArgs e)
    {
        if (!alreadyLoaded)
        {
            alreadyLoaded = true;
            OnViewLoaded(e);
        }
    }

    #region ViewLoaded event

    event EventHandler<EventArgs> viewLoaded;

    /// <summary>
    /// Occurs when the view has been loaded.
    /// </summary>
    public event EventHandler<EventArgs> ViewLoaded
    {
        add
        {
            viewLoaded += value;
        }
        remove
        {
            viewLoaded -= value;
        }
    }

    /// <summary>
    /// Closes the view.
    /// </summary>
    /// <param name="force">if set to <c>true</c> the control will be forced
    /// to close even if e.g., there is unsaved data and the user chooses 
    /// to cancel the closure.</param>
    /// <returns></returns>
    public virtual bool Close(bool force)
    {
        var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
        return viewService.CloseView(this, force);
    }

    /// <summary>
    /// Raises the <see cref="E:ViewLoaded"/> event.
    /// </summary>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void OnViewLoaded(EventArgs e)
    {
        if (viewLoaded != null)
        {
            viewLoaded(this, e);
        }
    }
    #endregion

    /// <summary>
    /// Gets a value indicating whether the control is in a designer.
    /// </summary>
    /// <value><c>true</c> if design time; otherwise, <c>false</c>.</value>
    protected bool DesignTime
    {
        get
        {
            return DesignerProperties.GetIsInDesignMode(this);
        }
    }

    #region IActiveAware and related

    void SetViewAwareAssociations(IViewModel oldViewModel, IViewModel newViewModel)
    {
        var oldViewAware = oldViewModel as IViewAware;
        var newViewAware = newViewModel as IViewAware;
        if (oldViewAware != null)
        {
            oldViewAware.DetachActiveAware();
        }

        if (newViewAware != null)
        {
            newViewAware.Attach(activeAwareUIElementAdapter);
        }
    }

    readonly ActiveAwareUIElementAdapter activeAwareUIElementAdapter;

    bool IActiveAware.IsActive
    {
        get
        {
            return activeAwareUIElementAdapter.IsActive;
        }
        set
        {
            activeAwareUIElementAdapter.IsActive = value;
        }
    }

    event EventHandler IActiveAware.IsActiveChanged
    {
        add
        {
            activeAwareUIElementAdapter.IsActiveChanged += value;
        }
        remove
        {
            activeAwareUIElementAdapter.IsActiveChanged -= value;
        }
    }

    #endregion
}

We see that the dependency property ViewModel, when changed, prompts the attachment of the ActiveAwareUIElement instance to the ViewModel. The mechanism for performing this is via the IViewAware implementation. If an IViewModel wishes to be aware of its view's state, in particular when it becomes active or inactive; without having to have a direct reference to the view, then it can implement the IViewAware interface.

 

IViewAware Interface


/// <summary>
/// Provides for advanced presentation behaviour in a <see cref="IViewModel"/>s.
/// </summary>
public interface IViewAware
{
    /// <summary>
    /// Attaches the specified active aware instance so that changes in the <see cref="IActiveAware.IsActive"/>
    /// state can be monitored.
    /// </summary>
    /// <param name="activeAware">The active aware.</param>
    void Attach(IActiveAware activeAware);

    /// <summary>
    /// Detaches the active aware instance. Changes in the <see cref="IActiveAware.IsActive"/>
    /// state will no longer be monitored.
    /// </summary>
    void DetachActiveAware();
}

The ViewModel base class implementation is provided next in full:

/// <summary>
/// A base implementation of the <see cref="IViewModel"/> interface.
/// </summary>
public abstract class ViewModelBase : IViewModel, INotifyPropertyChanged, IViewAware
{
    IActiveAware activeAwareInstance;

    protected ViewModelBase()
    {
        notifier = new PropertyChangeNotifier(this);
    }

    #region Title Property

    object title;

    [Description("The text to display on a tab.")]
#if !SILVERLIGHT
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
#endif        
    public object Title
    {
        get
        {
            return title;
        }
        set
        {
            notifier.Assign("Title", ref title, value);
        }
    }

    #endregion

    #region Property Change Notification
    readonly PropertyChangeNotifier notifier;

    protected PropertyChangeNotifier Notifier
    {
        get
        {
            return notifier;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            notifier.PropertyChanged += value;
        }
        remove
        {
            notifier.PropertyChanged -= value;
        }
    }

    protected AssignmentResult Assign<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        return notifier.Assign(propertyName, ref property, newValue);
    }

    #endregion

    #region Active Aware

    void IViewAware.Attach(IActiveAware activeAware)
    {
        ReplaceActiveAware(activeAware);
    }

    void IViewAware.DetachActiveAware()
    {
        ReplaceActiveAware(null);
    }

    void ReplaceActiveAware(IActiveAware activeAwareInstance)
    {
        if (this.activeAwareInstance != null)
        {
            this.activeAwareInstance.IsActiveChanged -= OnIsActiveChanged;
        }
        this.activeAwareInstance = activeAwareInstance;
        if (activeAwareInstance != null)
        {
            activeAwareInstance.IsActiveChanged += OnIsActiveChanged;
        }
    }

    bool lastActiveState;

    void OnIsActiveChanged(object sender, EventArgs e)
    {
        Notifier.NotifyChanged("Active", lastActiveState, Active);
        lastActiveState = Active;
    }

    /// <summary>
    /// Gets a value indicating whether this instance is being notified 
    /// of when it becomes active or inactive, 
    /// this may occur for example when its view gains focus or loses focus.
    /// </summary>
    /// <value><c>true</c> if monitoring the active state 
    /// of its view; otherwise, <c>false</c>.</value>
    public bool ActiveAware
    {
        get
        {
            return activeAwareInstance != null;
        }
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="ViewModelBase"/> 
    /// is active within the user interface.
    /// </summary>
    /// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
    public bool Active
    {
        get
        {
            return activeAwareInstance != null ? activeAwareInstance.IsActive : false;
        }
    }

    #endregion

    public override string ToString()
    {
        return title != null ? title.ToString() : base.ToString();
    }
}

So there it is. Just one way to provide a ViewModel with active awareness. Please be aware that this code is preliminary and may be subject to change. The full source code will be available via the Calcium source download page soon.

 

Thanks to Kent Boogaart for his post on MVVM Infrastructure ActiveAwareCommand which the ActiveAwareUIElementAdapter was inspired from.



Synchronous Invocation of Delegates with the Silverlight Dispatcher

clock January 10, 2010 18:14 by author Daniel Vaughan

Introduction

In this post I would like to briefly discuss the System.Windows.Threading.Dispatcher class, and the significant differences between its Silverlight and Desktop CLR implementations. We are going to look at solving two things:

  1. Consuming the UI’s Dispatcher in Silverlight before a page has been instanciated.
  2. Allowing for synchronous invocation of delegates on the UI thread.

Background

Recently my good friend Sacha Barber published an article, and in the comments of which we briefly touched on a cross threading issue that we have both experienced with the Silverlight Dispatcher class. I thought it was about time that I wrote some of this stuff up. This is the result.

Silverlight Thread Affinity

When working with Silverlight, one has to contend with the usual single threaded apartment model (STA) that is also present when working with WPF or Windows Forms. This means that one must interact with UI components, in particular DependencyObject derived types, from the thread in which they were created on. Fortunately Silverlight/WPF/Windows Forms includes infrastructure that makes acquiring and invoking calls on the UI thread simpler; specifically the System.Windows.Threading.Dispatcher, which is a prioritized message loop that handles thread affinity.

There is an excellent article describing the Dispatcher in detail.

Consuming the UI’s Dispatcher

In Silverlight a Dispatcher becomes associated with the main page during initialization, thereby making it available via the Applications RootVisual property like so:

Application.Current.RootVisual.Dispatcher

We can consume the Dispatcher this way, as long as we do so after the RootVisual has been defined. But in the case where we would like to consume the Dispatcher from the get-go, it leaves us out in the cold. Fortunately though, the Silverlight Dispatcher instance is also available via the System.Windows.Deployment.Current.Dispatcher property. This instance is defined when the Application starts, thereby making it possible to commence asynchronous operations before the first page is instanciated.

Synchronous Invocation of Delegates on the UI Thread

The Silverlight Dispatcher is geared for asynchronous operations. As we can see from the following image, unlike the Desktop CLR Dispatcher, the Silverlight Dispatcher class’s Invoke method overloads have internal visibility.

It has to be said that the Desktop CLR Dispatcher, when compared with the Silverlight version, as with many other classes, has a much richer API. In order to provide a means to synchronously invoke a delegate on the UI thread we need another approach. The approach I have taken is to utilize the System.Windows.Threading.DispatcherSynchronizationContext

By using the Post and Send methods of the DispatcherSynchronizationContext we are able to regain the synchronous Invoke capabilities within the Silverlight environment.

I have rolled this up into a set of reusable classes, located in my core Silverlight library, which you can find in the DanielVaughan.Silverlight project in the Calcium http://www.calciumsdk.net download.

/// <summary>
/// Singleton class providing the default implementation 
/// for the <see cref="ISynchronizationContext"/>, specifically for the UI thread.
/// </summary>
public class UISynchronizationContext : ISynchronizationContext
{
    DispatcherSynchronizationContext context;
    Dispatcher dispatcher;
    
    #region Singleton implementation

    static readonly UISynchronizationContext instance = new UISynchronizationContext();
    
    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    /// <value>The singleton instance.</value>
    public static ISynchronizationContext Instance
    {
        get
        {
            return instance;
        }
    }

    #endregion

    public void Initialize()
    {
        EnsureInitialized();
    }

    readonly object initializationLock = new object();

    void EnsureInitialized()
    {
        if (dispatcher != null && context != null)
        {
            return;
        }

        lock (initializationLock)
        {
            if (dispatcher != null && context != null)
            {
                return;
            }

            try
            {
                dispatcher = Deployment.Current.Dispatcher;
                context = new DispatcherSynchronizationContext(dispatcher);
            }
            catch (InvalidOperationException)
            {
                /* TODO: Make localizable resource. */
                throw new ConcurrencyException("Initialised called from non-UI thread."); 
            }
        }
    }

    public void Initialize(Dispatcher dispatcher)
    {
        ArgumentValidator.AssertNotNull(dispatcher, "dispatcher");
        lock (initializationLock)
        {
            this.dispatcher = dispatcher;
            context = new DispatcherSynchronizationContext(dispatcher);
        }
    }

    public void InvokeAsynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentValidator.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Post(callback, state);
    }

    public void InvokeAsynchronously(Action action)
    {
        ArgumentValidator.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            dispatcher.BeginInvoke(action);
        }
    }

    public void InvokeSynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentValidator.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Send(callback, state);
    }

    public void InvokeSynchronously(Action action)
    {
        ArgumentValidator.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            context.Send(delegate { action(); }, null);
        }
    }

    public bool InvokeRequired
    {
        get
        {
            EnsureInitialized();
            return !dispatcher.CheckAccess();
        }
    }
}

A further advantage of using either a Silverlight or Desktop CLR implementation of the ISynchronizationContext is that we are able to write CLR agnostic code. That is, code that was written for the Desktop CLR can be easily moved to the Silverlight.

Using the code:

UISynchronizationContext.Instance.InvokeSynchronously(delegate
                                {
/* Code to execute on the UI thread. */
                                });

Conclusion

In this post we have looked at consuming the UI’s Dispatcher in Silverlight as soon as an Application starts. We also saw how it is possible in Silverlight to accomplish synchronous invocation of delegates on the UI thread.

The full source shown in this article is available on the Calcium Codeplex site

 

 

 



Using T4 to Generate Pack URIs for XAML Files

clock November 25, 2009 15:25 by author Daniel Vaughan

Yesterday my fellow WPF Disciple Paul Stovell got me thinking about resolving XAML file paths.

As Paul points out, there doesn't appear to be an easy way to locate the URI for a XAML file. Internally, the generated .g.cs makes use of the path, as shown in the following excerpt:

public void InitializeComponent() 
{ 
  if (_contentLoaded) 
  { 
    return; 
  } 
  _contentLoaded = true; 
  System.Uri resourceLocater = new System.Uri("/PageCollection;component/pages/page1.xaml", System.UriKind.Relative); 
  #line 1 "..\..\..\Pages\Page1.xaml" 
  System.Windows.Application.LoadComponent(this, resourceLocater); 
  #line default 
  #line hidden 
}

But, how can we get our hands on it? What I’ve done is to incorporate the generation of XAML resource pack URIs into the T4 template I did a little while ago.

To demonstrate I have created a dummy UserControl in a subfolder in the sample application.

Image Figure: Dummy UserControl has a pack URI generated

 

The resulting output from the T4 template now enables us to determine the path to the XAML file in a safe way. The following excerpt shows the generated Pack URI:

namespace CSharpDesktopClrDemo.XamlMetadata.Folder1.Folder2.Metadata
{
    /// <summary>Metadata for XAML UserControl1.xaml</summary>
    public static class UserControl1XamlMetadata
    {
            /// <summary>Resource pack URI for XAML file.</summary>
            public const string XamlPackUri 
= @"/DanielVaughan.MetaGen.Demo;component/Folder1/Folder2/UserControl1.xaml"; } }

Now we have this, we can write:

Uri uri = new Uri(CSharpDesktopClrDemo.XamlMetadata.Folder1.Folder2.Metadata
                 .UserControl1XamlMetadata.XamlPackUri, UriKind.Relative);
var control = System.Windows.Application.LoadComponent(uri) 
       as DanielVaughan.MetaGen.Demo.Folder1.Folder2.UserControl1;

No more magic string pack URIs!

Download the template and sample application: MetaGen_01_04.zip (393.35 kb)

 



Compile-Time Validation of Composite Object Data Binding Expressions

clock November 7, 2009 18:06 by author Daniel Vaughan

Introduction

Prompted by a recent comment on the T4 Metadata Generation template article, which I released some weeks ago, I have implemented a new mechanism for concatenating property paths. This allows compile time validation of properties that exist on composite or nested members.

Background

Previously I have demonstrated how generated metadata can be used to provide compile-time validation of binding expressions. Rather than using string literals in binding expressions, one is able to use the x:Static markup extension and a T4 generated constant to indicate the binding path; as shown in the following excerpt.

<Label Content="{Binding Path={x:Static Metadata:PersonMetadata.NamePath}}"/>

Overcoming Limitations

This approach works fine when targeting a property from a single instance in a DataContext, but what happens when we wish to target a nested instance’s property? For example, and as demonstrated in the downloadable sample from the article mentioned above, if we have a ListBox populated with Person instances, and we wish to bind a label to the listbox’s SelectedItem.Address.StreetAddress property, we can do so using the following XAML:

<ListBox x:Name="listBox" Background="Black">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Orientation="Horizontal">
        <Label Content="{Binding Path={x:Static Metadata:PersonMetadata.NamePath}}"/>
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
<Label Content="{Binding ElementName=listBox, 
    Path={Demo:JoinPath 
                SelectedItem, 
                {x:Static Metadata:PersonMetadata.Address}, 
                {x:Static Metadata:AddressMetadata.StreetLine}}}"/>

Here we see a custom MarkupExtension called JoinPathExtension is used to enable the concatenation of path strings to create a PropertyPath that is used to target the nested Address instance. In this case, the string values of ‘SelectedItem’, ‘Address’, and ‘StreetLine’ combine to produce a PropertyPath ‘SelectedItem.Address.StreetLine’.

You will notice, when you open the CS Window1.xaml file in the sample download, that errors are reported for the Path expressions. These don’t prevent the designer from loading in either Visual Studio or Blend. They are, however, annoying.

 

Diagram: Visual Studio Xaml designer errors.

Attempting to resolve this issue I switched to using named arguments. No luck there either I’m afraid, with the x:Static expression resulting in a compile time error:

(Unknown property 'Converter' for type 'MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension' encountered while parsing a Markup Extension. Line x position Y)

My fellow disciple Philipp Sumi has a great post outlining the VS designer bug. 

I have experimented with a number of approaches, including (as Philipp suggests) explicit property syntax, and have settled on the one shown above.

The main parts of the JoinPathExtension are shown:

CS:

/// <summary>
/// Allows a set of property path strings to be concatenates 
/// into a <see cref="PropertyPath"/> instance.
/// </summary>
[MarkupExtensionReturnType(typeof(PropertyPath))]
public class JoinPathExtension : MarkupExtension
{
  readonly List<string> members = new List<string>(); 
 
  public JoinPathExtension()
  {
    /* Intentionally left blank. */
  }
 
  public JoinPathExtension(string member0)
  {
    if (member0 == null)
    {
      throw new ArgumentNullException("member0");
    }
    members.Add(member0);
  }
 
  public JoinPathExtension(string member0, string member1)
    : this(member0)
  {
    if (member1 == null)
    {
      throw new ArgumentNullException("member1");
    }
    members.Add(member1);
  }
 
  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    var path = string.Join(".", members.ToArray());
    var result = new PropertyPath(path);
    return result;
  }
 
  void SetMember(int index, string value)
  {
    if (value == null)
    {
      throw new ArgumentNullException("value");
    }
    if (members.Count < index + 1)
    {
      members.Add(value);
      return;
    }
    members[index] = value;
  }
 
  #region Named member properties
  [ConstructorArgument("member0")]
  public string Member0
  {
    get
    {
      return members[0];
    }
    set
    {
      SetMember(0, value);
    }
  }
 
  public string Member1
  {
    get
    {
      return members[1];
    }
    set
    {
      SetMember(1, value);
    }
  }
 
}

VB.NET:

Imports System.Windows.Markup

Public Class JoinPathExtension
    Inherits MarkupExtension
    ' Methods
    Public Sub New()
        Me.members = New List(Of String)
    End Sub

    Public Sub New(ByVal memberList As String())
        Me.members = New List(Of String)
        If (memberList Is Nothing) Then
            Throw New ArgumentNullException("memberList")
        End If
        Me.members.AddRange(memberList)
    End Sub

    Public Sub New(ByVal member1 As String)
        Me.members = New List(Of String)
        If (member1 Is Nothing) Then
            Throw New ArgumentNullException("member1")
        End If
        Me.members.Add(member1)
    End Sub

    Public Sub New(ByVal member1 As String, ByVal member2 As String)
        Me.New(member1)
        If (member2 Is Nothing) Then
            Throw New ArgumentNullException("member2")
        End If
        Me.members.Add(member2)
    End Sub

    Public Overrides Function ProvideValue(ByVal serviceProvider As IServiceProvider) As Object
        Return New PropertyPath(String.Join(".", Me.members.ToArray), New Object(0 - 1) {})
    End Function


    ' Properties
    <ConstructorArgument("member1")> _
    Public Property Member() As String
        Get
            Return Me.members.Item(0)
        End Get
        Set(ByVal value As String)
            If (value Is Nothing) Then
                Throw New ArgumentNullException("value")
            End If
            If (Me.members.Count < 1) Then
                Me.members.Add(value)
            Else
                Me.members.Item(0) = value
            End If
        End Set
    End Property

    <ConstructorArgument("member2")> _
    Public Property Member2() As String
        Get
            Return Me.members.Item(1)
        End Get
        Set(ByVal value As String)
            If (value Is Nothing) Then
                Throw New ArgumentNullException("value")
            End If
            If (Me.members.Count < 2) Then
                Me.members.Add(value)
            Else
                Me.members.Item(1) = value
            End If
        End Set
    End Property


    ' Fields
    Private ReadOnly members As List(Of String)
End Class

Conclusion

We have seen how by using a custom MarkupExtension we are able to concatenate generated property name constants to produce PropertyPaths, which can be consumed by Path binding expressions. Having the capability to join path expression adds a lot to the flexibility of the generated metadata approach. We are now able to fully express property paths for nested objects in binding expressions, without resorting to string literals; increasing dramatically the flexibility of this approach.

Download the sample code from here.

 

 



Calling Web Services from Silverlight after the Browser has closed

clock October 19, 2009 23:07 by author Daniel Vaughan

Introduction

Today I was reading an excellent post by my fellow Disciple Laurent Bugnion, which led on to a short discussion about performing actions after a user attempts to close a browser window. It got me thinking about the capability to dispatch a web service call in Silverlight just after a user attempts to close the browser window or navigate elsewhere. I have been asked the question before, yet before now, have not attempted to answer it. So today I decided to do some experimenting.

There are two things I wanted to look at. Firstly, I wanted to allow a web service to be called after the Silverlight application’s Exit event is raised. Secondly, I wanted to provide the Silverlight application with the opportunity to cancel, or at least interrupt the close window process.

In the first scenario I found the way I could achieve the call after the Silverlight applications Exit event was raised, was to call a JavaScript method from Silverlight, and then finally a PageMethod; to perform the housekeeping.

 

Figure: A web service is called after the user closes the browser.

We see that in our App.xaml.cs the Exit event handler is called, which then calls a JavaScript function, as demonstrated in the following excerpt:

void Application_Exit(object sender, EventArgs e)
{
    HtmlPage.Window.Invoke("NotifyWebserviceOnClose", 
          new[] { guid.ToString() });
}

We then call a PageMethod on the page hosting the application like so:

function NotifyWebserviceOnClose(identifier) {
    PageMethods.SignOff(identifier, OnSignOffComplete);            
}

The PageMethod is where we are able to update a list of connected clients, or do whatever takes our fancy.

[WebMethod]
[System.Web.Script.Services.ScriptMethod] 
public static void SignOff(string identifier)
{
    /* Update your list of signed in users here etc. */
    System.Diagnostics.Debug.WriteLine("*** Signing Off: " + identifier);
}

Caveat lector- According to Laurent this is not reliable. So, you will need to test it for yourself. I’ve tested it locally in Firefox 3.5 and IE8. But, your results may differ.

Now the second thing I wanted to look at was the ability to provide some interaction with the user, either cancelling or calling a web service, when the user attempts to close the browser; but before the browser is actually closed.

The way I did this was to place an onbeforeunload handler in the body tag of the page, as shown:

<body onbeforeunload="OnClose()">

I then created the OnClose handler in a JavaScript script block as shown in the following excerpt:

function OnClose() {           
    var plugin = document.getElementById("silverlightControl");
    var shutdownManager = plugin.content.ShutdownManager;
    var shutdownResult = shutdownManager.Shutdown();
    if (shutdownResult) {
        event.returnValue = shutdownResult;
    }
}

Here we see that we retrieve the Silverlight application and resolve an instance of the ShutdownManager class present in the Silverlight App class. The ShutdownManager is a class that is able to perform some arbitrary duty when the application is closing. It is made accessible to the host webpage by registering it with a call to RegisterScriptableObject.

void Application_Startup(object sender, StartupEventArgs e)
{
    HtmlPage.RegisterScriptableObject("ShutdownManager", shutdownManager);
    this.RootVisual = new MainPage();
}

ShutdownManager

The ShutdownManager is decorated with two attributes that allow it to be consumed by an external source. They are SciptableType and ScriptableMember. As we see below, they are used to allow the ShutdownManager class to be called via Ajax.

[ScriptableType]
public class ShutdownManager
{
    [ScriptableMember]
    public string Shutdown()
    {
        string result = null;
        var page = (MainPage)Application.Current.RootVisual;

        if (page.Dirty)
        {
            var messageBoxResult = MessageBox.Show(
                "Save changes you have made?", "Save changes?", MessageBoxButton.OKCancel);
            if (messageBoxResult == MessageBoxResult.OK)
            {
                var waitingWindow = new WaitingWindow();
                waitingWindow.Show();

                waitingWindow.NoticeText = "Saving work on server. Please wait...";
                page.SetNotice("Saving work on server. Please wait...");

                var client = new ShutdownServiceClient();
                client.SaveWorkCompleted += ((sender, e) =>
                    {
                        waitingWindow.NoticeText = "It is now safe to close this window.";
                        page.SetNotice("It is now safe to close this window.");
                        page.Dirty = false;
                        waitingWindow.Close();
                    });
                client.SaveWorkAsync("Test id");
                result = "Saving changes.";
            }
            else
            {
                result = "If you close this window you will lose any unsaved work.";
            }
        }

        return result;
    }
}

Here we see that once the Shutdown method is called we test if the main (Silverlight) page is dirty. If so, we prompt the user to save changes. If changes are to be saved we dispatch our async web service call using a service client. The key thing to note here is that we are returning a string value if the page is deemed dirty. This string value then indicates to the consuming JavaScript code that the window should not be closed immediately. Once the call makes it back to our JavaScript onbeforeload handler, we assign the event.returnValue the value of this string. If this value is not an empty string it will cause the web browser to display a dialog; asking the user whether they really wish to leave the page.

Figure: Browser prompts with ‘Are you sure ...’ message.

In the demo application accompanying this post, you will notice that we simulate a user modification. This is done by ticking the Page dirty checkbox.

 

Figure: The demonstration application simulates a modification to the data model.

Unticked, closing the browser will result in the window closing immediately. However, ticked will cause the presentation of a Save Changes dialog, as shown below.

Figure: Save changes dialog prompts user.

What happens if the user cancels the operation? Remember our event.returnValue string in the JavaScript OnClose function. If the user cancels, then this value is assigned a string value of “If you close this window you will lose any unsaved work.” We can see how the browser chooses to display the string shown below:

Figure: User is prompted with browser initiated dialog.

Obviously we must deal with when the user selects the nominal Ok button in the save dialog. To provide a somewhat more agreeable user experience, I have dropped in a Silverlight ChildWindow control, with a ProgressBar control for good measure.

Figure: A progress bar is presented to the user while saving.

Once the user hits Ok on the Save Changes dialog, that web service call is dispatched almost immediately. In order for the web service call to occur though, we must perform interrupt the thread so that our Silverlight App has a chance to dispatch the call. We can do that with a JavaScript alert call, or as we see here, we assign the event.returnValue, which causes a dialog to be displayed in much the same manner.

Conclusion

In this post we have seen how it is possible to call a web service after a Silverlight App’s Exit event has been raised. While this may not be reliable, I’d be interested to know your experience with this approach, and certainly of any alternate approaches. Finally we looked at interacting with the user, to allow cancellation of a close window event or to perform last minute web service calls. This was achieved using the onbeforeunload event and a JavaScript handler.

Thank you for reading.

Download the sample (557.06 kb)



Banishing String Literals from XAML Resource References

clock October 3, 2009 19:15 by author Daniel Vaughan

Introduction

Since my initial experimentation with generating project metadata data using T4 (Text Template Transformation Toolkit), there have been several obvious opportunities to expand its scope. One such opportunity has been to use T4 to generate static properties representing XAML keys. This serves to reduce the reliance on string literals when referencing resources. I have subsequently augmented my MetadataGeneration.tt template to do just that.

x:Key Property Generation

To demonstrate, I have updated the sample application provided with my previous article, and employed a couple ResourceDictionaries to show how we can reference a ‘default’ dictionary using constant names, and also how we can cross reference with an auxiliary ResourceDictionary, overriding the resources using constant name values.

In the following excerpt we see a button that has its Background defined using a Resource whose key is defined as a static property in a generated class.

<Button 
Background="{StaticResource {x:Static Keys:MainDictionaryXamlMetadata.ButtonBackgroundKey}}"
Margin
="0,5,0,0" Content="Change" HorizontalAlignment="Left" Click="Button_ChangeClick"/>

This is useful, because it means if we modify the name of the background brush in the ResourceDictionary and forget to update references to it, we will be alerted at compile time, rather than at runtime.

The MetadataGeneration.tt template scours your project looking for XAML files, and then generates classes for them containing all x:Key attributes, represented as static properties. As we can see in the following excerpt, that the ButtonBackGround key is defined as a LinearGradientBrush in the MainDictionary.xaml.

MainDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <LinearGradientBrush x:Key="ButtonBackground">
        <GradientStop Color="AliceBlue" Offset="0" />
        <GradientStop Color="Yellow" Offset=".7" />
    </LinearGradientBrush>
    <SolidColorBrush x:Key="WindowForegroundBrush" Color="White"/>
</ResourceDictionary>

Being able to reference one ResourceDictionary from another is useful. If we take another ResourceDictionary, which redefines the resources of the first, we are able to do so in a safer way; expressing our intent with a dedicated property, and using the non-literal string key names derived from the MainDictionary.xaml.

SecondaryDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Metadata="clr-namespace:CSharpDesktopClrDemo.XamlMetadata.Folder1.Metadata">
    <LinearGradientBrush x:Key="{x:Static Metadata:MainDictionaryXamlMetadata.ButtonBackgroundKey}">
        <GradientStop Color="AliceBlue" Offset="0" />
        <GradientStop Color="Blue" Offset=".7" />
    </LinearGradientBrush>
    <SolidColorBrush x:Key="{x:Static Metadata:MainDictionaryXamlMetadata.WindowForegroundBrushKey}" Color="Azure"/>
</ResourceDictionary>

So, we can define our resources wherever we like; in a separate assembly for example, yet we still retain compile time validation of resource key references.

App.xaml

<Application x:Class="DanielVaughan.MetaGen.Demo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <ResourceDictionary Source="pack://application:,,,/DanielVaughan.MetaGen.Demo;component/Folder1/MainDictionary.xaml"/>
        <!--<ResourceDictionary Source="pack://application:,,,/DanielVaughan.MetaGen.Demo;component/Folder1/SecondaryDictionary.xaml"/>-->
    </Application.Resources>
</Application>

Implementation

To accomplish the discovery of XAML files and associated Keys, and the subsequent generation of metadata classes, during project traversal we must do two things: detect when the project item is a XAML file, and keep a track of the current project directory. Now accomplishing the first is easy. Detecting when the current project item is a project folder, on the other hand, turned out to be hack-worthy, as you will notice in the following excerpt.

string processingDirectory = string.Empty;

public void ProcessProjectItem(ProjectItem projectItem,
    Dictionary<string, NamespaceBuilder> namespaceBuilders, string activeNamespace)
{
    FileCodeModel fileCodeModel = projectItem.FileCodeModel;

    if (fileCodeModel != null)
    {
        foreach (CodeElement codeElement in fileCodeModel.CodeElements)
        {
            WalkElements(codeElement, null, null, namespaceBuilders);
        }
    }
    
    string activeNamespaceCopy = activeNamespace;
    if (string.IsNullOrEmpty(activeNamespaceCopy))
    {
        if (string.IsNullOrEmpty(xamlRootNamespace))
        {
            activeNamespaceCopy = rootNamespace; 
        }
        else
        {
            activeNamespaceCopy = string.Format("{0}.{1}", 
                rootNamespace, xamlRootNamespace);
        }
    }
    
    if (projectItem.ProjectItems != null 
        && projectItem.ProjectItems.Count > 0)
    {
        /* This is a hack to determine if we have a directory.
            If you know the proper way for doing this, please let me know. */
        try
        {
            var foo = projectItem.Document;
        }
        catch (Exception ex)
        {
            string newNamespace = projectItem.Name.Replace(" ", string.Empty); 
            activeNamespaceCopy += "." + newNamespace; 
        }
    }
    
    string itemName = projectItem.Name; 
    if (generateXamlKeys && itemName.EndsWith(".xaml", true, CultureInfo.InvariantCulture))
    {    
        /* Retrieve or create the namespace builder. */
        NamespaceBuilder namespaceBuilder;

        if (!namespaceBuilders.TryGetValue(activeNamespaceCopy, out namespaceBuilder))
        {
            namespaceBuilder = new NamespaceBuilder(activeNamespaceCopy, null, 0);
            namespaceBuilders[activeNamespaceCopy] = namespaceBuilder;
        }
        
        string fileName = projectItem.get_FileNames(0);
        string text = System.IO.File.ReadAllText(fileName);
        MatchCollection matches = xClassRegex.Matches(text);                

        if (matches.Count > 0)
        {
            string xamlMetadataClassName = ConvertProjectItemNameToTypeOrMemberName(itemName.Substring(0, itemName.Length - 4));                
            var classComments = new List<string> {string.Format("/// <summary>Metadata for XAML {0}</summary>", itemName)};
            XamlBuilder xamlBuiler = new XamlBuilder(xamlMetadataClassName, classComments, 1);
            namespaceBuilder.AddChild(xamlBuiler);
            
            foreach (Match match in matches)
            {
                Group keyGroup = match.Groups["KeyName"];
                string keyName = keyGroup.Value;
                var keyComments = new List<string> {string.Format("/// <summary>Represents x:Key=\"{0}\"/></summary>", keyName)};
                xamlBuiler.AddChild(new XamlKeyBuilder(keyName, keyComments));
            }
        }
    }

    if (projectItem.ProjectItems != null)
    {
        foreach (ProjectItem childItem in projectItem.ProjectItems)
        {
            ProcessProjectItem(childItem, namespaceBuilders, activeNamespaceCopy);
        }
    }
}

We see that generating XAML metadata works in the same way as the class and interface metadata generation, in that we represent the XAML file using a XamlBuilder, and keys within the XAML file are represented as XamlKeyBuilders.

Generating Namespaces for XAML Metadata Classes

To avoid collisions with type names and generated namespace, I offer a customizable xamlRootNamespace configuration variable. This variable is used to construct namespace names for generated XAML metadata classes as the following example illustrates:

If we have a XAML file called Window1.xaml. It will be represented by a class named [generatedClassPrefix]Window1[generatedXamlClassSuffix][generatedClassSuffix]

Conclusion

We have seen how XAML Resource keys, ordinarily referenced using magic strings, can be eliminated using generated Type and File metadata.

I am still rather pleased at what one is able to achieve by combining T4 and the DTE. Visual Studio 2010 will see T4 move to a more visible position within the IDE. This, together with the new features of T4 in VS2010, will surely make it an indispensible tool.

To download the template source and demo applications, please visit the updated T4 Metadata article on Codeproject.

 



MetaGen: A project metadata generator for Visual Studio using T4

clock August 15, 2009 16:31 by author Daniel Vaughan

I am rather excited to share with you something that I have been working on in my spare time for the last couple of days. I have used T4 to build a metadata generator for your Silverlight and Desktop CLR projects. It can be used as a replacement for static reflection (expression trees), reflection (walking the stack), and various other means for deriving the name of a property, method, or field.

There has been much discussion’s around removing the property name string code smell from INotifyPropertyChanged implementations. Reflection is slow, and various techniques using reflection have been proposed, but have been criticized for contributing to decreased application performance. It now seems reasonable that a language extension for property change notification might be in order. But, as we don’t have that yet, I have created the next best thing: a generator.

A couple of days ago I began exploring T4 (Text Template Transformation Toolkit), and I’m loving it. T4, if you don’t already know is versatile templating system that allows you to generate classes, sql scripts etc. from within Visual Studio. If you have Visual Studio 2008, then you already have T4 ready to go! To find out more about T4 visit http://msdn.microsoft.com/en-us/library/bb126445.aspx

How to use it

To use MetaGen, simply include the attached MetaGen.tt file in your project. That’s it!

The MetaGen.tt has a number of customizable constants to prevent name collisions in your project.

/// <summary>
/// The modifier to use when outputting classes.
/// </summary>
const string generatedClassAccessModifier = "internal";

/// <summary>
/// The prefix to use for output class and interface names.
/// The combination of this and <see cref="generatedClassSuffix"/> provides
/// MetaGen with the ability to identify those classes etc.,
/// for which it should generated metadata, and to ignore MetaGen generated classes.
/// </summary>
const string generatedClassPrefix = "";

/// <summary>
/// The suffix to use for output class and interface names.
/// The combination of this and <see cref="generatedClassSuffix"/> provides
/// MetaGen with the ability to identify those classes etc.,
/// for which it should generated metadata, and to ignore MetaGen generated classes.
/// </summary>
const string generatedClassSuffix = "Metadata";

/// <summary>
/// The child namespace in which to place generated items.
/// If there is a class in MyNamespace namespace,
/// the metadata class will be generated
/// in the MyNamespace.[generatedClassSuffix] namespace.
/// This string can be null or empty, in which case a subnamesapce
/// will not be created, and generated output will reside
/// in the original classes namespace.
/// </summary>
const string generatedNamespace = "Metadata";

/// <summary>
/// The number of spaces to insert for a one step indent.
/// </summary>
const int tabSize = 4;

Template Implementation

The template consists of a procedural portion of code that retrieves the Visual Studio EnvDTE.DTE instance. This allows us to manipulate the Visual Studio automation object model, and to retrieve file, class, and method information and so on. Thanks go out to Oleg Sych for the T4 Toolbox which demonstrated how to retrieve the EnvDTE.DTE from the template hosting environment.

Once we object the relevant EnvDTE.Project we are able to process the EnvDTE.ProjectItems (files and directories in this case) as the following excerpt shows:

    IServiceProvider hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
EnvDTE.ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
Project project = containingProjectItem.ContainingProject;

/* Build the namespace representations, which contain class etc. */
Dictionary<string, NamespaceBuilder> namespaceBuilders = new Dictionary<string, NamespaceBuilder>();
foreach (ProjectItem projectItem in project.ProjectItems)
{
ProcessProjectItem(projectItem, namespaceBuilders);
}

We then recursively process EnvDTE.CodeElements and directories in order to create an object model representing the project.

 

public void ProcessProjectItem(ProjectItem projectItem, Dictionary<string, NamespaceBuilder> namespaceBuilders)
{
FileCodeModel fileCodeModel = projectItem.FileCodeModel;

if (fileCodeModel != null)
{
foreach (CodeElement codeElement in fileCodeModel.CodeElements)
{
WalkElements(codeElement, null, null, namespaceBuilders);
}
}

if (projectItem.ProjectItems != null)
{
foreach (ProjectItem childItem in projectItem.ProjectItems)
{
ProcessProjectItem(childItem, namespaceBuilders);
}
}

}

int indent;

public void WalkElements(CodeElement codeElement, CodeElement parent,
BuilderBase parentContainer, Dictionary<string, NamespaceBuilder> namespaceBuilders)
{
indent++;
CodeElements codeElements;

if (parentContainer == null)
{
NamespaceBuilder builder;
string name = "global";
if (!namespaceBuilders.TryGetValue(name, out builder))
{
builder = new NamespaceBuilder(name, null, 0);
namespaceBuilders[name] = builder;
}
parentContainer = builder;
}

switch(codeElement.Kind)
{
/* Handle namespaces */
case vsCMElement.vsCMElementNamespace:
{
CodeNamespace codeNamespace = (CodeNamespace)codeElement;
string name = codeNamespace.FullName;
if (!string.IsNullOrEmpty(generatedNamespace) && name.EndsWith(generatedNamespace))
{
break;
}

NamespaceBuilder builder;

if (!namespaceBuilders.TryGetValue(name, out builder))
{
builder = new NamespaceBuilder(name, null, 0);
namespaceBuilders[name] = builder;
}

codeElements = codeNamespace.Members;
foreach (CodeElement element in codeElements)
{
WalkElements(element, codeElement, builder, namespaceBuilders);
}
break;
}
/* Process classes */
case vsCMElement.vsCMElementClass:
{
CodeClass codeClass = (CodeClass)codeElement;
string name = codeClass.Name;
if (string.IsNullOrEmpty(generatedNamespace)
&& name.StartsWith(generatedClassPrefix)
&& name.EndsWith(generatedClassSuffix))
{
break;
}

List<string> comments = new List<string>();
comments.Add(string.Format("/// <summary>Metadata for class <see cref=\"{0}\"/></summary>", codeClass.FullName));

BuilderBase builder;
if (!parentContainer.Children.TryGetValue(name, out builder))
{
builder = new ClassBuilder(name, comments, indent);
parentContainer.Children[name] = builder;
}
codeElements = codeClass.Members;
if (codeElements != null)
{
foreach (CodeElement ce in codeElements)
{
WalkElements(ce, codeElement, builder, namespaceBuilders);
}
}
break;
}
/* Process interfaces. */
case vsCMElement.vsCMElementInterface:
{
CodeInterface codeInterface = (CodeInterface)codeElement;
string name = codeInterface.Name;
if (name.StartsWith(generatedClassPrefix) && name.EndsWith(generatedClassSuffix))
{
break;
}
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeInterface.FullName);
comments.Add(string.Format("/// <summary>Metadata for interface <see cref=\"{0}\"/></summary>", commentName));
InterfaceBuilder builder = new InterfaceBuilder(name, comments, indent);
parentContainer.AddChild(builder);

codeElements = codeInterface.Members;
if (codeElements != null)
{
foreach (CodeElement ce in codeElements)
{
WalkElements(ce, codeElement, builder, namespaceBuilders);
}
}
break;
}
/* Process methods */
case vsCMElement.vsCMElementFunction:
{
CodeFunction codeFunction = (CodeFunction)codeElement;
if (codeFunction.Name == parentContainer.Name
|| codeFunction.Name == "ToString"
|| codeFunction.Name == "Equals"
|| codeFunction.Name == "GetHashCode"
|| codeFunction.Name == "GetType"
|| codeFunction.Name == "MemberwiseClone"
|| codeFunction.Name == "ReferenceEquals")
{
break;
}

string name = codeFunction.Name.Replace('.', '_');
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeFunction.FullName);
comments.Add(string.Format("/// <summary>Name of method <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
break;
}
/* Process properties. */
case vsCMElement.vsCMElementProperty:
{
CodeProperty codeProperty = (CodeProperty)codeElement;

string name = codeProperty.Name.Replace('.', '_');
if (name != "this")
{
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeProperty.FullName);
comments.Add(string.Format("/// <summary>Name of property <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
}
break;
}
/* Process fields. */
case vsCMElement.vsCMElementVariable:
{
CodeVariable codeVariable = (CodeVariable)codeElement;
string name = codeVariable.Name;
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeVariable.FullName);
comments.Add(string.Format("/// <summary>Name of field <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
break;
}
}
indent--;
}

Once this is complete we output our namespace representations to the resulting MetaGen.cs file like so:

/* Finally, write them to the output. */
foreach (object item in namespaceBuilders.Values)
{
WriteLine(item.ToString());
}

What results is a file containing various namespace blocks that include static classes representing our non-metadata classes and interfaces with the project. Property names, method names, and field names are represented as constants. Inner classes are represented as nested static classes.

I have included with this post the MetaGen.tt template file, and also a demo WPF application. If you have any suggestions such as ideas for other metadata information etc., please let me know.

MetaGen.tt (14.92 kb)  (Just the T4 template)

MetaGen.zip (60.08 kb)  (The T4 template and an demo application)

Update: I've published a more recent article on CodeProject with a newer enriched template and examples.

 



Property Change Notification using a Weak Referencing Strategy

clock August 2, 2009 22:03 by author Daniel Vaughan

Features

  • Desktop and Silverlight CLR compatibility
  • Capability to perform assignment and raise appropriate events before and after assignment.
  • Weak referenced
  • Provides for both expression tree and loosely typed strings
  • Uses extended EventArgs to supply before and after values
  • Extended PropertyChangingEventArgs for cancellable changes
  • Configurable to use caching of EventArgs to decrease heap fragmentation
  • Comes with unit tests for Desktop and Silverlight CLRs

Introduction

INotifyPropertyChanged is a ubiquitous part of Silverlight and WPF programming. It is used extensively in WPF and Silverlight to enable non-DependencyObjects to signal that a bound value is out of date. I’ve seen many approaches that have been used in order to remove the property name string requirement. Some have employed lambda expressions, or walking the stack, while others have used generated proxies or AOP point cuts. This post and the accompanying code are not so much about ridding us from the loosely typed string, although I do provide the means if you don’t mind a performance hit. Today there is another code smell that I would like to address, and it is the use of base classes for property change notification.

In this post I will demonstrate how we are able to encapsulate two property change interface implementations (INotifyPropertyChanged and INotifyPropertyChanging) in a reusable class, and in a weak referencing manner to avoid any possible leakage.

Using a base class implementation for INotifyPropertyChanged has never sat easy with me. It probably goes back to early 2003 after reading Juval Lowy’s landmark book Programming .NET Components. The principal of favouring aggregation over inheritance is a driver for creating shallow class hierarchies and maintainable components. It is a principal that offers many advantages, and one that I strongly recommend.

Using the Library

DanielVaughan.ComponentModel .PropertyChangeNotifier is the main class. You use it by creating a field in your owner class, and instanciating it within the owner’s constructor. We apply the boiler plate code, which consists of a ‘flow-through’ interface implementation for either or both INotifyPropertyChanged and INotifyPropertyChanging.

An example is shown in the following excerpt.

class MockNotifyPropertyChanged : INotifyPropertyChanged, INotifyPropertyChanging
{
    readonly PropertyChangeNotifier notifier;

    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            notifier.PropertyChanged += value;
        }
        remove
        {
            notifier.PropertyChanged -= value;
        }
    }
    #endregion

    #region INotifyPropertyChanging Implementation
    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            notifier.PropertyChanging += value;
        }
        remove
        {
            notifier.PropertyChanging -= value;
        }
    }
    #endregion

    public MockNotifyPropertyChanged()
    {
        notifier = new PropertyChangeNotifier(this);
    }

    int int1;

    public int TestPropertyAssigned
    {
        get
        {
            return int1;
        }
        set
        {
            notifier.Assign(
                "TestPropertyAssigned", ref int1, value);
        }
    }

    string string1;

    public string TestPropertyAssignedLambda
    {
        get
        {
            return string1;
        }
        set
        {
            notifier.Assign(
                (MockNotifyPropertyChanged mock) => mock.TestPropertyAssignedLambda, 
                ref string1, value);
        }
    }    
}

The two property examples shown, delegate the task of assigning the property to the PropertyChangeNotifier. This provides the PropertyChangeNotifier with the opportunity to raise the PropertyChanging event of the INotifyPropertyChanging interface, perform the assignment (or return if a handler called Cancel() on the EventArgs, then raise the PropertyChangedEvent from the INotifyPropertyChanged interface.

Implementation

The following excerpt is taken from the PropertyChangeNotifier class. It contains both Silverlight and Desktop CLR specific sections.

/// <summary>
/// This class provides an implementation of the <see cref="INotifyPropertyChanged"/>
/// and <see cref="INotifyPropertyChanging"/> interfaces. 
/// Extended <see cref="PropertyChangedEventArgs"/> and <see cref="PropertyChangingEventArgs"/>
/// are used to provides the old value and new value for the property. 
/// <seealso cref="PropertyChangedEventArgs{TProperty}"/>
/// <seealso cref="PropertyChangingEventArgs{TProperty}"/>
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
public sealed class PropertyChangeNotifier : INotifyPropertyChanged, INotifyPropertyChanging
{
    readonly WeakReference ownerWeakReference;

    /// <summary>
    /// Gets the owner for testing purposes.
    /// </summary>
    /// <value>The owner.</value>
    internal object Owner
    {
        get
        {
            if (ownerWeakReference.Target != null)
            {
                return ownerWeakReference.Target;
            }
            return null;
        }
    }

    /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangeNotifier"/> class.
    /// </summary>
    /// <param name="owner">The intended sender 
    /// of the <code>PropertyChanged</code> event.</param>
    public PropertyChangeNotifier(object owner) : this(owner, true)
    {
    }

    /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangeNotifier"/> class.
    /// </summary>
    /// <param name="owner">The intended sender 
    /// <param name="useExtendedEventArgs">If <c>true</c> the
    /// generic <see cref="PropertyChangedEventArgs{TProperty}"/>
    /// and <see cref="PropertyChangingEventArgs{TProperty}"/> 
    /// are used when raising events. 
    /// Otherwise, the non-generic types are used, and they are cached 
    /// to decrease heap fragmentation.</param>
    /// of the <code>PropertyChanged</code> event.</param>
    public PropertyChangeNotifier(object owner, bool useExtendedEventArgs)
    {
        ArgumentValidator.AssertNotNull(owner, "owner");
        ownerWeakReference = new WeakReference(owner);
        this.useExtendedEventArgs = useExtendedEventArgs;
    }

    #region event PropertyChanged

#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangedEventHandler propertyChanged;

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanged += value;
        }
        remove
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanged -= value;
        }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyChanged"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanged != null)
        {
            propertyChanged(owner, e);
        }
    }

    #endregion

    /// <summary>
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// Assignment nor notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<T, TProperty>(
        Expression<Func<T, TProperty>> expression, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        string propertyName = GetPropertyName(expression);
        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    void AssignWithNotificationAux<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        /* Boxing may occur here. We should consider 
         * providing some overloads for primitives. */
        if (Equals(property, newValue)) 
        {
            return;
        }

        if (useExtendedEventArgs)
        {
            var args = new PropertyChangingEventArgs<TProperty>(propertyName, property, newValue);

            OnPropertyChanging(args);
            if (args.Cancelled)
            {
                return;
            }

            var oldValue = property;
            property = newValue;
            OnPropertyChanged(new PropertyChangedEventArgs<TProperty>(
                propertyName, oldValue, newValue));
        }
        else
        {
            var args = RetrieveOrCreatePropertyChangingEventArgs(propertyName);
            OnPropertyChanging(args);

            var changedArgs = RetrieveOrCreatePropertyChangedEventArgs(propertyName);
            OnPropertyChanged(changedArgs);
        }
    }

    readonly Dictionary<string, string> expressions = new Dictionary<string, string>();

    /// <summary>
    /// Notifies listeners that the specified property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="oldValue">The old value before the change occured.</param>
    /// <param name="newValue">The new value after the change occured.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<TProperty>(
        string propertyName, TProperty oldValue, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }
        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        if (ReferenceEquals(oldValue, newValue))
        {
            return;
        }

        var args = useExtendedEventArgs
            ? new PropertyChangedEventArgs<TProperty>(propertyName, oldValue, newValue)
            : RetrieveOrCreatePropertyChangedEventArgs(propertyName);

        OnPropertyChanged(args);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Notifies listeners that the property has changed.
    /// Notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="oldValue">The old value of the property before it was changed.</param>
    /// <param name="newValue">The new value of the property after it was changed.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<T, TResult>(
        Expression<Func<T, TResult>> expression, TResult oldValue, TResult newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNull(expression, "expression");

        string name = GetPropertyName(expression);
        NotifyChanged(name, oldValue, newValue);
    }
    
    static MemberInfo GetMemberInfo<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        var member = expression.Body as MemberExpression;
        if (member != null)
        {
            return member.Member;
        }

        /* TODO: Make localizable resource. */
        throw new ArgumentException("MemberExpression expected.", "expression");
    }

    #region INotifyPropertyChanging Implementation
#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangingEventHandler propertyChanging;

    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging += value;
        }
        remove
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging -= value;
        }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyChanging"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanging(PropertyChangingEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanging != null)
        {
            propertyChanging(owner, e);
        }
    }
    #endregion

#if SILVERLIGHT
    readonly object expressionsLock = new object();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        lock (expressionsLock)
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                if (!expressions.TryGetValue(expression.ToString(), out name))
                {
                    var memberInfo = GetMemberInfo(expression);
                    if (memberInfo == null)
                    {
                        /* TODO: Make localizable resource. */
                        throw new InvalidOperationException("MemberInfo not found.");
                    }
                    name = memberInfo.Name;
                    expressions.Add(expression.ToString(), name);
                }
            }
        }

        return name;
    }
#else
    readonly ReaderWriterLockSlim expressionsLock = new ReaderWriterLockSlim();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        expressionsLock.EnterUpgradeableReadLock();
        try
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                expressionsLock.EnterWriteLock();
                try
                {
                    if (!expressions.TryGetValue(expression.ToString(), out name))
                    {
                        var memberInfo = GetMemberInfo(expression);
                        if (memberInfo == null)
                        {
                            /* TODO: Make localizable resource. */
                            throw new InvalidOperationException("MemberInfo not found.");
                        }
                        name = memberInfo.Name;
                        expressions.Add(expression.ToString(), name);
                    }
                }
                finally
                {
                    expressionsLock.ExitWriteLock();
                }
            }
        }
        finally
        {
            expressionsLock.ExitUpgradeableReadLock();
        }
        return name;
    }
#endif

    bool cleanupOccured;

    bool OwnerDisposed
    {
        get
        {
            /* We slightly improve performance here 
             * by avoiding multiple Owner property calls 
             * after the Owner has been disposed. */
            if (cleanupOccured)
            {
                return true;
            }
            var owner = Owner;
            if (owner != null)
            {
                return false;
            }
            cleanupOccured = true;
            var changedSubscribers = propertyChanged.GetInvocationList();
            foreach (var subscriber in changedSubscribers)
            {
                propertyChanged -= (PropertyChangedEventHandler)subscriber;
            }
            var changingSubscribers = propertyChanging.GetInvocationList();
            foreach (var subscriber in changingSubscribers)
            {
                propertyChanging -= (PropertyChangingEventHandler)subscriber;
            }

            /* Events should be null at this point. Nevertheless... */
            propertyChanged = null;
            propertyChanging = null;
            propertyChangedEventArgsCache.Clear();
            propertyChangingEventArgsCache.Clear();

            return true;
        }
    }

    [Conditional("DEBUG")]
    void ValidatePropertyName(string propertyName)
    {
#if !SILVERLIGHT
        var propertyDescriptor = TypeDescriptor.GetProperties(Owner)[propertyName];
        if (propertyDescriptor == null)
        {
            /* TODO: Make localizable resource. */
            throw new Exception(string.Format(
                "The property '{0}' does not exist.", propertyName));
        }
#endif
    }

    bool useExtendedEventArgs;
    readonly Dictionary<string, PropertyChangedEventArgs> propertyChangedEventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();
    readonly Dictionary<string, PropertyChangingEventArgs> propertyChangingEventArgsCache = new Dictionary<string, PropertyChangingEventArgs>();

#if SILVERLIGHT
    readonly object propertyChangingEventArgsCacheLock = new object();

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName, 
            propertyChangingEventArgsCacheLock, 
            propertyChangingEventArgsCache, 
            x => new PropertyChangingEventArgs(x));

        return result;
    }

    readonly object propertyChangedEventArgsCacheLock = new object();

    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName,
            propertyChangedEventArgsCacheLock,
            propertyChangedEventArgsCache,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    static TArgs RetrieveOrCreateEventArgs<TArgs>(
        string propertyName, object cacheLock, Dictionary<string, TArgs> argsCache, 
        Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;

        lock (cacheLock)
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }

            result = createFunc(propertyName);
            argsCache[propertyName] = result;
        }
        return result;
    }
#else
    readonly ReaderWriterLockSlim propertyChangedEventArgsCacheLock = new ReaderWriterLockSlim();
    
    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangedEventArgsCache,
            propertyChangedEventArgsCacheLock,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    readonly ReaderWriterLockSlim propertyChangingEventArgsCacheLock = new ReaderWriterLockSlim();

    static TArgs RetrieveOrCreateArgs<TArgs>(string propertyName, Dictionary<string, TArgs> argsCache,
        ReaderWriterLockSlim lockSlim, Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;
        lockSlim.EnterUpgradeableReadLock();
        try
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }
            lockSlim.EnterWriteLock();
            try
            {
                if (argsCache.TryGetValue(propertyName, out result))
                {
                    return result;
                }
                result = createFunc(propertyName);
                argsCache[propertyName] = result;
                return result;
            }
            finally
            {
                lockSlim.ExitWriteLock();
            }
        }
        finally
        {
            lockSlim.ExitUpgradeableReadLock();
        }
    }

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangingEventArgsCache,
            propertyChangingEventArgsCacheLock,
            x => new PropertyChangingEventArgs(x));

        return result;
    }
#endif

}

 

I’ve extended the PropertyChangedEventArgs and the PropertyChangingEventArgs to include before and after values. The following excerpt shows the PropertyChangedEventArgs.

/// <summary>
/// Provides data for the <see cref="INotifyPropertyChanged.PropertyChanged"/> event,
/// exposed via the <see cref="PropertyChangeNotifier"/>.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
public sealed class PropertyChangedEventArgs<TProperty> : PropertyChangedEventArgs
{
    /// <summary>
    /// Gets the value of the property before it was changed.
    /// </summary>
    /// <value>The old value.</value>
    public TProperty OldValue { get; private set; }
        /// <summary>
    /// Gets the new value of the property after it was changed.
    /// </summary>
    /// <value>The new value.</value>
    public TProperty NewValue { get; private set; }
        /// <summary>
    /// Initializes a new instance 
    /// of the <see cref="PropertyChangedEventArgs{TProperty}"/> class.
    /// </summary>
    /// <param name="propertyName">Name of the property that changed.</param>
    /// <param name="oldValue">The old value before the change occured.</param>
    /// <param name="newValue">The new value after the change occured.</param>
    internal PropertyChangedEventArgs(
        string propertyName, TProperty oldValue, TProperty newValue) 
        : base(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

INotifyPropertyChanging doesn’t exist in Silverlight, so I’ve implemented it.

In order to turn of the extended EventArgs, pass an extra argument to the constructor. By turning of the extended EventArgs, we enable to arg caching feature. I implemented this after reading Josh Smith’s nice articles on the subject.

The PropertyChangeNotifier retains a link to the Owner using a WeakReference. Each time a change is handled, the PropertyChangeNotifier checks to see if the Owner is still alive. If it isn’t, the PropertyChangeNotifier will remove all event handlers.

Unit Tests

The download includes various unit tests for both the Desktop and Silverlight environments. I recommend examining them, to further your understanding about how it all works.

Figure: Desktop CLR test results.

 

Figure: Silverlight CLR test results.

 

Future Enhancements

  • Batch support
  • Event Suppression

Download source code for the Desktop and Silverlight CLRs: Core_01_01.zip (1.42 mb)



Transparent WCF Channel Management with Unity

clock July 30, 2009 13:55 by author Daniel Vaughan

Introduction

It is generally considered good form to define a separate ServiceContract interface for all WCF services. By doing so, it decouples the contract from the implementation. Still, if we consume a service contract via conventional means such as generating a proxy using a ChannelFactory or using a ServiceReference generated proxy, we couple the service with the WCF infrastructure. So what could be wrong with that? Well, say if we have a local version of a service, and a remote implementation of the service, one must be retrieved using a WCF specific measure like those just mentioned, while the other can be registered with our DI container and retrieved that way. But the code that is shared between the local and remote implementations must be aware of this, and this inhibits reusability. Wouldn’t it be better if retrieval of all services was done homogeneously? We can indeed achieve this, and without any coupling to the WCF infrastructure. I will explain how in just a moment, but first some background.

Background

I choose to manage my service channels in a centralized way. I do this by using an IChannelManager implementation. You can find out more about the use of the IChannelManager implementation in a number of my articles. In particular here and here

There are a several advantages to this approach, not least of which is that the Channel Manager takes care of caching channel proxies, and recreating them if and when a fault occurs.

Using Unity’s IStaticFactoryConfiguration to Transparently Retrieve Proxies

Those familiar with Unity will know that to registers a type or an instance with the container, one can either define the association in config or at runtime by calling the Containers RegisterInstance or RegisterType methods. There is, however, a third approach: we can register a factory method to perform the retrieval by configuring the IStaticFactoryConfiguration. This enables us to have Unity drive the generation or retrieval of a channel proxy using the ChannelManagerSingleton.

Figure: Retrieval of a service channel via Unity.

Firstly we must register the extension with Unity. The extension can be found in the Microsoft.Practices.Unity.StaticFactory.dll assembly, and should be reference by your project. We then add the extension to our Unity container like so:

Container.AddNewExtension<StaticFactoryExtension>();

We are then able to have Unity retrieve the IChannelManager instance in order to retrieve the service proxy when it is requested.

var channelManager = UnitySingleton.Container.Resolve<IChannelManager>();
UnitySingleton.Container.Configure<IStaticFactoryConfiguration>().RegisterFactory<IMyService>(
container => channelManager.GetChannel<IMyService>());

Thus, we no longer need to retrieve the IChannelManager in our code in order to retrieve the channel proxy. All we need do is grab it straight from Unity like so:

var myService = UnitySingleton.Container.Resolve<IMyService>();

Opening the channel, testing connectivity, caching it etc., is all done behind the scenes!

There is a drawback to this approach. If, for whatever reason, the channel proxy is unable to be retrieved by the Channel Manager then Unity will raise a ResolutionFailedException, and the reason for the failure will be hidden as an inner exception. So, we must gear our code to be resilient to resolution failures and depend not only on Unit Tests discovering missing type registrations. This post covers simplex services, and I have still to explore an elegant way to achieve the same result for duplex services with their callback instances.

Conclusion

We have seen how, by using Unity, we are able to further abstract the retrieval of channel proxies. This allows us to consume services in the same way whether they be local or via WCF. By doing this we are able to achieve location agnostic service consumption, and increase the reusability of shared code across local and remote deployment scenarios. The source for the Channel Manager and other goodies can be found at http://calcium.codeplex.com/

 



A location agnostic Message Service

clock June 15, 2009 21:20 by author Daniel Vaughan

When developing an application, clearly it’s prudent to have uniformity in the manner certain tasks are carried out, thereby avoiding violation of the DRY principle. An example of this is displaying common dialog boxes. But wait, if you think this post is just going to be about an abstracted dialog box system, then think again. While Calcium does provide a common dialog system, it also allows us to display a dialog to the user from the server during any WCF call! Moreover, it allows us to consume the same api on the client and the server. This means we are able to interact with the user directly, without having to e.g., interpret the result of a WCF call in the client. It effectively narrows the tier gap.

In this post, firstly we will discuss the client-side message service, then we will examine how it is leveraged from the server-side to provide the location agnosticism.

So, obviously it’s unwise for each member of a development team to be creating his or her own dialogs for simple tasks such as asking the user a closed ended question (e.g. a Yes/No question box). If we decide to change the caption in the dialogs across the board, it is rather more difficult if dialogs are scattered across the project. Likewise if we wish to port the application from WPF to Silverlight, or even to a command line interface, think Powershell or mobile applications, it’s great to be able to swap out the implementation for any given scenario. Clearly an abstracted system is in order.

Out of the box, Calcium comes with a number of Message Service implementations. There is a client implementation for WPF, another client-side implementation for command line driven applications, and a server-side implementation that sends messages back to the client via a callback, and leverages the client-side IMessageService implementation.

The Message Service has various overloads which allow the caption and message to be specified.

Let’s take a look at the IMessageService interface and client-side implementations.

Diagram: IMessageService allows us to interact with the user in a UI agnostic manner.

The Message Service allows for a Message Importance level to be specified. This allows for a threshold noise level to be specified by the user. If the MessageImportance is lower than the user’s preference, then the user won’t be bothered with the message. In a later version of Calcium we shall see a Preferences Service for specifying the preferred level.

Diagram: MessageService and CommandLineMessageService both override ShowCustomDialog method of MessageServiceBase to cater for their particular environments.

By applying variation through merely overriding the ShowCustomDialog method it also makes it very easy to mock the MessageServiceBase class for testing.

Our client-side WPF implementation channels all messaging requests through the ShowCustomDialog method as shown in the following excerpt.

/// <summary>
/// WPF implementation of the <see cref="IMessageService"/>.
/// </summary>
public class MessageService : MessageServiceBase
{
    public override MessageResult ShowCustomDialog(string message, string caption,
        MessageButton messageButton, MessageImage messageImage, 
        MessageImportance? importanceThreshold, string details)
    {
        /* If the importance threshold has been specified 
         * and it's less than the minimum level required (the filter level) 
         * then we don't show the message. */
        if (importanceThreshold.HasValue && importanceThreshold.Value < MinumumImportance)
        {
            return MessageResult.OK;
        }

        if (MainWindow.Dispatcher.CheckAccess())
        {    /* We are on the UI thread, and hence no need to invoke the call.*/
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            return messageBoxResult.TranslateToMessageBoxResult();
        }

        MessageResult result = MessageResult.OK; /* Satisfy compiler with default value. */
        MainWindow.Dispatcher.Invoke((ThreadStart)delegate
        {
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            result = messageBoxResult.TranslateToMessageBoxResult();
        });

        return result;
    }

    static Window MainWindow
    {
        get
        {
            return UnitySingleton.Container.Resolve<IMainWindow>() as Window;
        }
    }
}

I have created various extension methods for translating between the native WPF enums MessageBoxButton, MessageBoxImage, and MessageBoxResult. Why go to all of this trouble? On first appearances it appears like an antipattern. The reason is in fact a good one: there are differences in these enums in WPF and Silverlight, and this allows us to cater for bother without duplication.

I am considering expanding the service to support message details, and perhaps reducing the number of overloads with a reference type Message parameter. Another improvement would be to implement a Don’t show again checkbox system. I leave that for a future incarnation. I may even use Karl Shifflet’s excellent Common TaskDialog project http://www.codeproject.com/KB/WPF/WPFTaskDialogVistaAndXP.aspx (with Karl's permission).

Server-side Message Service

We’ve looked at the basic client-side implementation of IMessageService, but now things are going to get a little more interesting. With Calcium we have the capability to consume the IMessageService in the same way on both the client and server. To accomplish this we use a WCF custom header and a duplex service callback.

The demonstration application contains a module called the MessageServiceDemoModule. The view for this module contains a single button that becomes enabled when the CommunicationService has connectivity with the server.

This is code in a WCF service, which we can execute asynchronously as shown, and present the user with dialogs on the client side. Remember, this code executes server-side!

        public void DemonstrateMessageService()
        {
            var messageService = UnitySingleton.Container.Resolve<IMessageService>();
            ThreadPool.QueueUserWorkItem(delegate
            {
                messageService.ShowMessage(
                    "This is a synchronous message invoked from the server." + 
                    "The service method is still executing and waiting for your response.",
                    "DemoService", MessageImportance.High);
                bool response1 = messageService.AskYesNoQuestion(
                    "This is a question invoked from the server. Select either Yes or No.",
                    "DemoService");
                string responseString = response1 ? "Yes" : "No";
                messageService.ShowMessage(string.Format(
                    "You selected {0} as a response to the last question.", responseString), 
                    MessageImportance.High);
            });
        }
    }

In the above excerpt, we retrieve the IMessageService instance from the Unity container. This is done in same manner as we would do on the client! Being location agnostic allows us to move business logic far more easily. I have placed the Message Service calls here inside a QueueUserWorkItem delegate just to demonstrate the independence of the Message Service, in that the WCF call returns almost immediately, yet a child thread will continue to work in the background and will still retain the capability to communicate with the user. Without using a child thread to communicate with the user, we may experience a timeout if the user fails to respond fast enough. Please note that we are required to retrieve the Message Service from the Unity container before the service call completes. Without doing so will raise an exception, as the OperationContext for the service call will no longer be present.

  Figure: Server causes dialog to be presented client-side.

 

Figure: Question asked from server.

Figure: Response received and echoed back to user.

 

How does it all work? When we need a service channel we use our IChannelManager instance to retrieve an instance.

I’ve discussed the IChannelManager in a couple of articles, in particular here and here here

In order to support identification of the application instance from any WCF service call we place a custom header into every service channel we create, as shown in the following excerpt from the ServiceManagerSingleton and InstanceIdHeader classes:

public static class InstanceIdHeader
{
    public static readonly String HeaderName = "InstanceId";
    public static readonly String HeaderNamespace 
            =  OrganizationalConstants.ServiceContractNamespace;
}

We use this static class to place the header, and also to retrieve the header on the server.

void AddCustomHeaders(IClientChannel channel)
{
    MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        InstanceIdHeader.HeaderName,
        InstanceIdHeader.HeaderNamespace,
        instanceId.ToString());

    var scope = new OperationContextScope(channel);
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
}

So, when we create the service channel we add the custom header as shown in the following excerpt.

public TChannel GetChannel<TChannel>()
{
    Type serviceType = typeof(TChannel);
    object service;

    channelsLock.EnterUpgradeableReadLock();
    try
    {
        if (!channels.TryGetValue(serviceType, out service))
        {    /* Value not in cache, therefore we create it. */
            channelsLock.EnterWriteLock();
            try
            {
                /* We don't cache the factory as it contains a list of channels 
                 * that aren't removed if a fault occurs. */
                var channelFactory = new ChannelFactory<TChannel>("*");

                service = channelFactory.CreateChannel();
                AddCustomHeaders((IClientChannel)service);

                var communicationObject = (ICommunicationObject)service;
                communicationObject.Faulted += OnChannelFaulted;
                channels.Add(serviceType, service);
                communicationObject.Open(); 
                ConnectIfClientService(service, serviceType);

                UnitySingleton.Container.RegisterInstance<TChannel>((TChannel)service);
            }
            finally
            {
                channelsLock.ExitWriteLock();
            }
        }
    }
    finally
    {
        channelsLock.ExitUpgradeableReadLock();
    }

    return (TChannel)service;
}

We cache the channel until it is closed or faults. But as soon as we create a new channel, we add the header to be consumed on the server. We also have duplex channel support, which works in much the same way.

We must talk to the ICommunicationService in order to create a callback, before we attempt to consume the Message Service on the server-side. This is done automatically by the CommunicationModule. In fact, the Communication Module polls the server periodically to let it know that it is still alive. It also will detect network connectivity, or the lack thereof, and disable or enable the polling accordingly.

/// <summary>
/// Notifies the server communication service that the client is still alive.
/// Occurs on a ThreadPool thread. <see cref="NotifyAlive"/>
/// </summary>
/// <param name="state">The unused state.</param>
void NotifyAliveAux(object state)
{
    try
    {
        if (!NetworkInterface.GetIsNetworkAvailable())
        {
            return;
        }
        var channelManager = UnitySingleton.Container.Resolve<IChannelManager>();
        var communicationService 
            = channelManager.GetDuplexChannel<ICommunicationService>(callback);
        communicationService.NotifyAlive();
        if (!connected)
        {
            connected = true;
            connectionEvent.Publish(ConnectionState.Connected);
        }
        lastExceptionType = null;
    }
    catch (Exception ex)
    {
        Type exceptionType = ex.GetType();
        if (exceptionType != lastExceptionType)
        {
            Log.Warn("Unable to connect to communication service.", ex);
        }
        lastExceptionType = exceptionType;
        if (connected)
        {
            connected = false;
            connectionEvent.Publish(ConnectionState.Disconnected);
        }
    }
    finally
    {
        connecting = false;
    }
}

As an aside, the Communication Module itself has no UI. Its purpose is to interact with the CommunicationService, and to provide notifications to the client when various server-side events take place. One such is CompositeEvent is the ConnectionEvent, which can be seen in the previous excerpt.

I plan on expanding the Message Service system to allow text input, drop down list selection, and maybe even custom dialogs.

To find out more about Calcium please see the CodePlex site.



About the author

Daniel VaughanDaniel Vaughan is a software developer with a decade of commercial experience across a wide range of industries including e-commerce, multimedia, and finance. While originally from Australia and the UK, Daniel is currently based in Geneva Switzerland; working with WPF, WCF, and WF within the finance industry. In his spare time Daniel likes to spend time thinking up novel ideas, such as employing neural networks to predict user navigation behaviour in WPF applications, and a grid computing framework for Silverlight. Daniel is also the creator of a number of open-source projects including Calcium, and Clog. E-mail me Send mail

 

I am an Insider

WPF and Silverlight Insiders


I am a WPF Disciple

Disciple

 

CodeProject MVP

RecentComments

Comment RSS

Sign in