WPF MVVM Busy Message With Ambient Context

By | February 16, 2014

Every .NET developer has already used an Ambient Context without knowing it. A typical Ambient Context is:

using (var tx = new TransactionScope())
{
    ....
}

The pattern is not very well known. Here is one good description I found: The Wandering Glitch – Ambient Context

I found this pattern very useful for displaying progress/busy messages in WPF applications. The following example shows how to build a simple Ambient Context for it.
The usage of it is slightly different than in the example above with the TransactionScope. I explain later on why.
(The code is on GitHub)

Usage of the BusyContext in a WPF/MVVM application:
The follow code has two nested BusyContext objects. The creation of a context causes the UI to change the progress text. After disposing a context, the previous context message is displayed.

BusyContext

BusyContext


You see, the usage of the BusyContext is fairly easy. The CreateBusyContext is a helper method in my base viewmodel class.
Here you see that the BusyContext constructor takes three parameter. Context store, busy message and a Action which gets triggered after creating and disposing the context. (Why I’m using a context store is explained at the end of the blog post.)

protected BusyContext CreateBusyContext(string message)
{
	var context = new BusyContext(this.BusyContextStore, message, this.NotifyBusyChanged);
	return context;
}

I put the most important classes here. The whole code is on GitHub.

The ContextStore is responsible for storing the contexts. The Current property returns the “deepest” (in nested contexts) context data.

public class ContextStore<TData> : IContextStore<TData>
{
	private Stack<AmbientContext<TData>> _Store = new Stack<AmbientContext<TData>>();

	public TData Current
	{
		get
		{
			if (_Store.Any())
				return _Store.Peek().Data;
			return default(TData);
		}
	}

	public void Add(AmbientContext<TData> context)
	{
		this._Store.Push(context);
	}

	public void Remove()
	{
		this._Store.Pop();
	}
}

The AmbientContext is the base class for the BusyContext:

public abstract class AmbientContext<TData> : IDisposable
{
    private IStoreWriter<TData> _Store;

    private Action _OnDispose;

    public TData Data { get; private set; }

    public AmbientContext(IStoreWriter<TData> store, TData data, Action onContextChange = null)
    {
        this.Data = data;
        _Store = store;
        this._OnDispose = onContextChange;

        _Store.Add(this);
        this.NotifyChange();
    }

    private void NotifyChange()
    {
        if (this._OnDispose != null)
            this._OnDispose();
    }


    public virtual void Dispose()
    {
        _Store.Remove();
        this.NotifyChange();
    }
}

My BusyContext looks quite simple.

public class BusyContext : AmbientContext<string>
{
	public BusyContext(IStoreWriter<string> store, string busyMessage, Action onContextChange = null)
		: base(store, busyMessage, onContextChange)
	{ }
}

Reason why the usage of my BusyContext is slightly different compared to other contexts:
Undefined storage scope:
I don’t want to restrict the context data to a specific scope. Restricting the context to the current Thread [ThreadStatic] (for example) would mean that I could display only one busy message. This is a huge restrication in a non-single window application like MDI/TDI. So therefore, you have to define a storage. This gives a lot of flexibility. For example: You could show multiple busy messages from same ViewModel in same time by defining two different stores.
No static classes/properties for getting the current busy message:
Static properties like Thread.CurrentPrincipal return the data of the current Ambient Context. In one way, this is very helpful. But it has some drawbacks when it comes to the unit testing. In my example the data of the current Ambient Context can be retrieved from the store object. The store object is a simple class an can be mocked in unit tests.

Leave a Reply

Your email address will not be published. Required fields are marked *


× 2 = eight