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

  • Full Stack Hands-On Development with .NET

    In the fast-paced realm of modern software development, proficiency across a full stack of technologies is not just beneficial, it's essential. Microsoft has an entire stack of open source development components in its .NET platform (formerly known as .NET Core) that can be used to build an end-to-end set of applications.

  • .NET-Centric Uno Platform Debuts 'Single Project' for 9 Targets

    "We've reduced the complexity of project files and eliminated the need for explicit NuGet package references, separate project libraries, or 'shared' projects."

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

Subscribe on YouTube