Code

There are several aspects of the StockViewer tutorial application that are quite interesting to review:

  • Background Updates

  • Broadcasting Messages

  • Modal User Interaction

  • DataBinding UI updates

Background Updates

In this application we have a static controller class StockManager that runs in the background in a single dedicated thread and uses the MarketService provider to query stock values.

The class is static (the code cannot create any instance of this class) and its functionality is shared by all users. Since the class is static we cannot use the constructor to start our background task, but we can use the static constructor, invoked the first time a user code references this class, to start our single shared task:

static StockManager()
{
    // starts a background service listening for stock updates.
    Task.Run(() => Listener());
}

StockPanel listens to the Update event and updates the browser through a push update when any value changes.

private void StockManager_Update(object sender, EventArgs e)
{
	// this event may come from an out-of-bound thread, bring it in context
	// and push the update to the browser.
	Application.Update(this, () =>
	{
		// update only  the stocks that have changed.
		// var model = (BindingList<Stock>)this.bindingSource.DataSource;
		var model = GetModel();
		if (model == null)
			return;

		var changed = false;
		foreach (var m in model)
		{
			if (m.Watching)
			{
				changed |= StockManager.UpdateQuote(m);
			}
		}

		if (changed)
			StockValuesChanged?.Invoke(this, EventArgs.Empty);
	});
}

The magic is done by the Application.Update(this, ...) call. When the code block is done, the system will push any update to the browser in real time.

Broadcasting Messages

The method Listener() runs on its own thread. It's quite a simple method, in fact it's just a loop that periodically retrieves the stock quotes from the MarketService provider and broadcasts the Update and Error events.

// Task runner.
private async static void Listener()
{
	while (true)
	{
		await Task.Delay(250);

		try
		{
			LastQuotes = MarketService.GetQuotes();

			// fire the update event to all listeners.
			Update?.Invoke(null, EventArgs.Empty);
		}
		catch (Exception ex)
		{
			Error?.Invoke(null, new ThreadExceptionEventArgs(ex));
		}
	}
}

We say broadcast instead of fire the events because they are static and since all events in .NET are multicast delegates (multiple listeners can attach to the same event). In our case, all users listen to the same single event fired by the StockManager class.

The MarketService simulator may throw an exception when the StockManager controller asks for the stock symbols to watch:

public static Quote[] GetQuotes()
{
	...
	
	foreach (var s in SYMBOLS)
	{
		...
		
		// random error, 1% of the times.
		if (rand.Next(100) == 1)
			throw new ApplicationException("Stock quotes are not available at this time.");
	}
	
	...
}

Since StockPanel uses StockManager.GetWatchableStocks() when it's displayed by handling the Appear event, if we are unable to get the list of stock symbols to watch we only have two options: try again or quit.

The cool and difficult part is the fact that this is a server-side process because the code has to run on the server to use a service that is not public and is not available to the whole world through a simple ajax call.

While running on the server, the code is able to ask the user on the client whether to continue or not and keep going within the same loop on the server! The result is a total server/client seamless integration that can be achieved with 1 line of code.

await MessageBox.ShowAsync(ex.Message, "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)

Data Binding UI Updates

We have seen that the StockViewer application needs to dynamically add panels to the DataRepeater to display the watchable stock symbols and to update the name, value and last update date for each stock being watched.

It's all done without any code. All the StockPanel does is to ask the StockManager to update the model:

foreach (var m in model)
{
	if (m.Watching)
	{
		changed |= StockManager.UpdateQuote(m);
	}
}

The method StockManager.UpdateQuote() doesn't know anything about the view. It simply sets the new value to the stock model Value property. If the value is different, the Stock model fires the PropertyChanged event.

public double Value
{
	get { return _value; }
	set
	{
		if (_value != value)
		{
			_value = value;
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));

		}
	}
}
private double _value;

The data binding system handles the PropertyChanged event and updates all the bound properties causing the UI to get immediately refreshed.

Last updated