C# Corner

All Talk: Using the Mediator Pattern in .NET Framework

How can one class communicate with each another, yet know nothing about that other class? That's where the mediator pattern comes in, and Eric shows how to employ it in a .NET chat application.

The mediator pattern is a common software design pattern that allows two classes to communicate without knowing about each other. What happens is that the two classes communicate via a mediator class and are only aware of the mediator. It's use becomes evident when the number of objects in your apps starts to grow and management of the classes starts to become unwieldy.

The core components of the mediator pattern are the mediator interface, the concrete mediator and the colleague classes. The mediator interface defines the contract by which the two colleague classes will communicate. The concrete mediator implements the mediator interface. And the colleague classes use the mediator interface to communicate. In a sense, the mediator class acts as a message bus between colleague classes.

To demonstrate the mediator pattern I'll create a simple Windows Store app that allows a user to send a message to one or many parties. The user will be able to register multiple users to the chatroom, then select from and to users for the message.

First, I add the IUser colleague interface. The IUser interface stores a chat user's name and messages. Furthermore, the interface allows a user to send and receive chat messages and it has an IChatroom instance:

public interface IUser
{
  String Name { get; set; }
  IChatroom Chatroom { get; set; }

  ObservableCollection<String> Messages { get; }

  void Send(IUser to, string message);
  void Receive(IUser from, string message);
}

Next, I implement the IUser interface through the User class. First, I implement the Name and Chatroom properties:

public string Name { get; set; }
public IChatroom Chatroom { get; set; }

Then, I implement the Messages properties, which store all received messages for a user:

public ObservableCollection<string> Messages { get; private set; } Then I implement the User class constructor that takes a name and initializes the Name and Messages properties:

 
public User(string name)
{
  Name = name;
  Messages = new ObservableCollection<string>();
}

Next, I implement the Send method, which dispatches the message to the destination user via the chat room:

 
public void Send(IUser to, string message)
{
  Chatroom.Send(this, to, message);
}

Last, I implement the Receive method, which constructs and adds a received message to the Messages collection:

 
public void Receive(IUser from, string message)
{
  Messages.Add(string.Format("{0} says {1}", from.Name, message));
}

See the completed User class implementation in Listing 1.

Listing 1: User.cs

 
using System.Collections.ObjectModel;

namespace VSMMediatorDemo
{
  public class User : IUser
  {
    public string Name { get; set; }
    public IChatroom Chatroom { get; set; }

    public ObservableCollection<string> Messages { get; private set; }

    public User(string name)
    {
      Name = name;
      Messages = new ObservableCollection<string>();
    }

    public void Send(IUser to, string message)
    {
      Chatroom.Send(this, to, message);
    }

    public void Receive(IUser from, string message)
    {
      Messages.Add(string.Format("{0} says {1}", from.Name, message));
    }
  }
}

Next, I add the IChatroom mediator interface that has Register and Send methods. The Register method adds a user to the chat room and the send method sends a text message from one user to another user:

 
public interface IChatroom
{
  void Register(IUser user);
  void Send(IUser from, IUser to, string message);
}

Then I implement the IChatroom interface via the Chatroom class. First, I add the _users primary member variable that stores the registered chat users:

 
private readonly List<IUser> _users = new List<IUser>();
Then I implement the Register method, which adds a user to the _users collection if they don't already exist and sets the Chatroom property on the user:

public void Register(IUser user) { if (!_users.Contains(user)) { _users.Add(user); } user.Chatroom = this; }

Last, I implement the Send method, which sends the message from the source user to the destination user:

 
public void Send(IUser from, IUser to, string message)
{
  var user = _users.Find(x => x == to);
  if (user != null)
    user.Receive(from, message);
}

See Listing 2 for the completed Chatroom class implementation.

Listing 2: Chatroom.cs

 
using System.Collections.Generic;

namespace VSMMediatorDemo
{
  public class Chatroom : IChatroom
  {
    private readonly List<IUser> _users = new List<IUser>();

    public void Register(IUser user)
    {
      if (!_users.Contains(user))
      {
        _users.Add(user);
      }

      user.Chatroom = this;
    }

    public void Send(IUser from, IUser to, string message)
    {
      var user = _users.Find(x => x == to);
      if (user != null)
        user.Receive(from, message);
    }
  }
}

Now that the mediator classes have been implemented, it's time to put them to good use within the Windows Store app. First, I add the UI for the application. I open up the MainPage.xaml file and copy the root StackPanel from Listing 3 into the root Grid element.

Listing 3: MainPage.xaml

 
<Page
  x:Class="VSMMediatorDemo.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:VSMMediatorDemo"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="20">
      <TextBlock>User Name</TextBlock>
      <TextBox Name="UserName"></TextBox>
      <Button Name="Register" Click="Register_Click">Register</Button>
      <TextBlock>From</TextBlock>
      <ListView Name="FromUser" ItemsSource="{Binding Mode=TwoWay}" MinHeight="100"
                         SelectionChanged="FromUser_SelectionChanged">
        <ListView.ItemTemplate>
          <DataTemplate>
            <TextBlock Name="Name" Text="{Binding Name}" Margin="10"></TextBlock>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
      <TextBlock>To</TextBlock>
      <ListView Name="ToUser" ItemsSource="{Binding Mode=TwoWay}" MinHeight="100" SelectionChanged="ToUser_SelectionChanged">
        <ListView.ItemTemplate>
          <DataTemplate>
            <TextBlock Name="Name" Text="{Binding Name}" Margin="10"></TextBlock>
          </DataTemplate>
        </ListView.ItemTemplate>
        </ListView>
        <TextBlock>Message</TextBlock>
        <TextBox Name="Message"></TextBox>
        <Button Name="Send" Click="Send_Click">Send</Button>
        <StackPanel Orientation="Horizontal">
          <TextBlock>Messages for:</TextBlock>
          <TextBlock Name="ChatTo"></TextBlock>
        </StackPanel>
        <ListView Name="ChatLog" ItemsSource="{Binding Mode=TwoWay}" MinHeight="100">
          <ListView.ItemTemplate>
            <DataTemplate>
              <TextBlock Name="Message" Text="{Binding}" Margin="10"></TextBlock>
            </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackPanel>
  </Grid>
</Page>

The final step is to implement the codebehind for the MainPage class. First, I add an IChatroom private member variable:

 
private readonly IChatroom _chatroom;

Then I add the _users private variable to keep track of the registered users in the chatroom:

 
private readonly ObservableCollection<IUser> _users;

Next, I add the _fromUser and _toUser private member variables to store the currently selected from and to chat users:

 
private IUser _fromUser;
private IUser _toUser;

Then in the MainPage constructor I initialize the _chatroom and _users variables. Furthermore, I set the ItemsSource for the FromUser and ToUser ListView controls, which both display all of the registered chat room users:

 
public MainPage()
{
  this.InitializeComponent();
  _chatroom = new Chatroom();
  _users = new ObservableCollection<IUser>();
  FromUser.ItemsSource = _users;
  ToUser.ItemsSource = _users;
}

Next, I implement the Register button click event handler that creates a new chat room user, registers them and adds them to the _users collection:

 
private void Register_Click(object sender, RoutedEventArgs e)
{
  IUser user = new User(UserName.Text);
  _chatroom.Register(user);
  _users.Add(user);
}

Then I implement the Send click button event handler that sends the entered message from the source to the destination user:

 
private void Send_Click(object sender, RoutedEventArgs e)
{
  _fromUser.Send(_toUser, Message.Text);
}

Next, I handle the FromUser ListView SelectionChanged event that sets the _fromUser to the selected user:

 
private void FromUser_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  _fromUser = FromUser.SelectedValue as IUser;
}

Last, I handle the ToUser ListView SelectionChanged event. When the user selects a new To user I set the _toUser to the selected user and set the ChatTo label to the destination user's name. I also set the ChatLog data context to the Messages property of the _toUser to rebind the ChatLog ListView to display the updated messages for the destination user:

 
private void ToUser_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  _toUser = ToUser.SelectedValue as IUser;
  ChatTo.Text = _toUser != null ? _toUser.Name : string.Empty;
  ChatLog.DataContext = _toUser.Messages;
}

The application is now complete and you should be able to register multiple chat room users and send messages between them as seen in Figure 1.

Finished Windows Store Chat App

[Click on image for larger view.]
Figure 1: Finished Windows Store Chat App

As you can see, the mediator pattern is a simple software design pattern to implement. It's used for decoupling the communication between classes within an application. You should consider using the mediator pattern if you have many classes that communicate among each other and managing the intercommunication between objects becomes increasingly cumbersome to maintain.

About the Author

Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube