Build p2p chat application with WPF and ASP.NET Web API

Β· 3469 words Β· 17 minutes to read

Recently I’ve been really enjoying myself with ASP.NET Web API. It is a tremendous beast, and with it’s self-hosting capabilities, it’s suitable as an HTTP-channeled-communication not only for ASP.NET websites, but also for any other .NET applications. In this post, I’m gonna try to show you the outcome of my weekend mash up - a peer-to-peer WPF chat application (clients connect directly to each other), fueled by ASP.NET Web API.

More after the jump.

What are we gonna do πŸ”—

So, as mentioned, we’ll try to build a chat application with WPF. It’s gonna be targeted for .NET 4.5 (possible to refactor it into .NET 4 easily as well), make use of of ASP.NET Web Api self-host, and allow us to communicate smoothly over the network through HTTP. Heck, we might even have to ditch Live and Skype by the end of this article.

With ASP.NET Web Api self-host, we can turn any C# product into a web server. What this means, is that each of our chat apps (or “clients”) will essentially be web servers, listening to incoming HTTP requests on a certain port. The idea is simple - if we have two chat “clients” out there in the network, and each of them is a web server with IP address and a port to which they listen - they can easily communicate between each other in a P2P way. We are going to use POST requests as a way to transport messages between the clients.

Getting started πŸ”—

We’ll start off with a normal WPF project, target it to .NET 4.5 and get the necessary self host packagaes from Nuget. To do that, go to Nuget and grab AspNetWebApi.SelfHost. If you do it from VS, it will automaticall get all dependencies as well. If not, you need the Core as well.

XAML grid and all that πŸ”—

Let’s start with the XAML UI. We’ll need the following pieces in our UI:

  • a textbox to display the chat conversation. It’s gonna keep scrolling down if text doesn’t fit anymore
  • a textbox to enter the message to send
  • a textbox to set username
  • a button to send the message
    These are all the core functionalities.

Additionally, a few configuration options will be needed in the UI as well:

  • a textbox to choose the local port to listen to (for incoming messages)
  • a textbox to enter the IP of the chat partner (for outgoing messages)
  • a button to start the chat with the provided settings (for, well, clicking)

Below is out wonderful UI in all its glory. It’s far from being a home run app GUI-wise, but for the purpose of this exercise it will have to suffice πŸ™‚

Let’s have a look at the XAML code required for setting this up.

<Grid>  
<Grid.RowDefinitions>  
<RowDefinition Height="*" />  
<RowDefinition Height="45" />  
<RowDefinition Height="15" />  
<RowDefinition Height="45" />  
</Grid.RowDefinitions>  
<TextBox Height="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Stretch" Margin="5, 5, 5, 5" Name="chatArea" VerticalAlignment="Stretch" Width="Auto" Grid.Row="0" IsReadOnly="True" />

<TextBox Height="25" HorizontalAlignment="Stretch" Margin="127,8,99,7" Name="inputText" VerticalAlignment="Stretch" Width="Auto" Grid.Row="1" KeyDown="userInputText_KeyDown" />

<TextBlock HorizontalAlignment="Right" Margin="0,2,418,0" Grid.Row="2" Grid.RowSpan="2" TextWrapping="Wrap" Text="Username" VerticalAlignment="Top" Width="61"/>  
<TextBlock HorizontalAlignment="Left" Margin="127,2,0,0" Grid.Row="2" Grid.RowSpan="2" TextWrapping="Wrap" Text="My Port" VerticalAlignment="Top"/>  
<TextBlock HorizontalAlignment="Left" Margin="247,2,0,0" Grid.Row="2" Grid.RowSpan="2" TextWrapping="Wrap" Text="Partner Address" VerticalAlignment="Top"/>

<TextBox Height="25" HorizontalAlignment="Stretch" Margin="5,5,374,5" Name="userName" VerticalAlignment="Stretch" Grid.Row="3" TextAlignment="Left" Text="Anonymous" />  
<TextBox Height="25" HorizontalAlignment="Stretch" Margin="124,6,258,5" Name="textBoxMyPort" VerticalAlignment="Stretch" Grid.Row="3" TextAlignment="Left" Text="900" />  
<TextBox Height="25" HorizontalAlignment="Stretch" Margin="244,8,99,7" Name="textBoxPartnerAddress" VerticalAlignment="Stretch" Grid.Row="3" TextAlignment="Left" Text="http://192.168.0.100:900" />  
<Button Content="Start chat!" HorizontalAlignment="Left" Margin="401,10,0,0" Grid.Row="3" VerticalAlignment="Top" Width="75" Click="startChat"/>  
<TextBlock HorizontalAlignment="Left" Width="84" Margin="36,14,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Your message:" VerticalAlignment="Top"/>  
<Button Content="Send" HorizontalAlignment="Left" Margin="401,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="click_sendMessage" />  
</Grid>  

Nothing very surprising here. We have 4 rows:

  • 1st will act as a chat text area,
  • 2nd is the input row for new messages,
  • then labels
  • and finally the settings row.

Notice the funky margins - I was too lazy and just arranged the elements on the designer manually πŸ™‚

I have bound 3 events:

  • KeyDown event for the chat input textbox - to detect Enter press and send the message
  • click on the send button - to do the same
  • click on the start chat button, which is to get the chat application up and running in the first place.

Organizing the code because GUI and Business shouldnt clash πŸ”—

I separated the application into three main namespaces: Filip.ChatGUI, Filip.ChatBusiness and Filip.ChatModels and their purpose is rather self explanatory. In the GUI part we’ll merely deal with setting and populating UI elements, while the whole communciation-between-clients extravaganza part will happen in the Business namespace. Models part will contain our, well, models: for the Message object and for MessageReceiver.

Let’s have a look at the GUI code:

private Filip.ChatBusiness.ChatProxy _cp { get; set; }  
public MainWindow()  
{  
InitializeComponent();  
}  

First of all, we’ll need a private instance of the ChatProxy class. We’ll go into details of that class later, so let’s leave it aside for now.

This ChatProxy property, is going to be the heart and soul of our application. It’s going to be instantiated by the user using a “start chat” button click (as long as proper settings are passed fro mthe GUI).

We will send messages using the following public method of MainWindow (which in turn calls a public method of ChatProxy):

private void sendMessage(Message m)  
{  
_cp.SendMessage(m);  
inputText.Clear();  
}  

In fact this method, and Status property, are the only public members that ChatProxy exposes to the GUI layer - after all, the GUI doesnt need ChatProxy for anything more (in the future you might want to add maybe methods to start/stop chat server and so on…).

Anyway, as you see, the message itself is going to be wrapped in the Message object. More on that class soon. In the
meantime, let’s think a little about how is this above method going to be called? Well, we need 3 aforementioned event handlers.

1. Handler for text input:

private void userInputText_KeyDown(object sender, KeyEventArgs e)  
{  
if (e.Key == Key.Enter) {  
if (_cp != null)  
{  
if (!string.IsNullOrEmpty(userName.Text) && !string.IsNullOrEmpty(inputText.Text))  
sendMessage(Message(userName.Text, inputText.Text));  
else  
ShowStatus("Nothing to send!");  
} else {  
ShowStatus("Chat not started!");  
}  
}  
}  

This is waiting for the Enter key press, and when it’s pressed and the ChatProxy is instantiated, and username and message text are provided, it sends the message (in the form of the Message class) to the Business layer.

2. Handler for clicking on send button:

private void click_sendMessage(object sender, RoutedEventArgs e)  
{  
if (_cp != null)  
{  
if (!string.IsNullOrEmpty(userName.Text) && !string.IsNullOrEmpty(inputText.Text))  
sendMessage(new Message(userName.Text, inputText.Text));  
else  
ShowStatus("Nothing to send!");  
}  
else  
{  
ShowStatus("Chat not started!");  
}  
}  

This is very similar as the previous one.

As you may have noticed, the handlers make use of two other methods - ShowStatus, used for displaying MessageBox upon erros, and ShowMessage() which will be used to display incoming messages from our chat partner.

Let’s have a look:

public void ShowMessage(Message m)  
{  
chatArea.Dispatcher.Invoke(  
DispatcherPriority.Normal,  
new Action(  
delegate()  
{  
chatArea.Text += ("[" + m.Sent + "] " + m.Username + ": " + m.Text);  
chatArea.Text += Environment.NewLine;  
chatArea.ScrollToEnd();  
}  
));  
}  
public void ShowStatus(string txt)  
{  
chatArea.Dispatcher.Invoke(  
DispatcherPriority.Normal,  
new Action(  
delegate() {MessageBox.Show(txt);}  
));  
}  

3. Clicking on start chat button:

private void startChat(object sender, RoutedEventArgs e)  
{  
if (!string.IsNullOrWhiteSpace(textBoxMyPort.Text) && !string.IsNullOrWhiteSpace(textBoxPartnerAddress.Text))  
{  
_cp = new Filip.ChatBusiness.ChatProxy(this.ShowMessage, this.ShowStatus, textBoxMyPort.Text, textBoxPartnerAddress.Text);  
if (_cp.Status)  
{  
chatArea.Text += ("Ready to chat!");  
chatArea.Text += Environment.NewLine;  
}  
}  
else  
{  
ShowStatus("Please fill in all the fields!");  
}  
}  

This button instantiates the ChatProxy and makes our chat ready to go.

In both cases the methods use Dispatcher.Invoke to update the UI. The reason for that is that we are going to pass them as delegates to ChatProxy object and invoke from there, so they are going to be called from a different thread.

Let’s come back for a moment to the ChatProxy constructor, as instantiating of it is arguably the most interesting thing happening in the GUI layer. The object is constructed accordingly:

_cp = new Filip.ChatBusiness.ChatProxy(this.ShowMessage, this.ShowStatus, textBoxMyPort.Text, textBoxPartnerAddress.Text);  

The constructor looks like this:

public ChatProxy(ShowReceivedMessage srm, ShowStatus sst, string myport, string partneraddress)  

where, the first two arguments are delegates, which we will use for updating the GUI from the Business layer. The other two are going to be used to start our local self-host server (myport) and to tell HttpClient who to send the messages to (partneraddress).

Modelling, anyone? πŸ”—

OK, so seems we have covered the entire GUI layer, and we can now smoothly transition to looking at the models.
Let’s a have quick peek at the message class.

public class Message  
{  
public string Username { get; set; }  
public string Text { get; set; }  
public DateTime Sent { get; set; }  
public FormUrlEncodedContent serializedMessage { get; set; }

public Message(MessageReceiver m)  
{  
Username = m.Username;  
Text = m.Text;  
Sent = DateTime.Now;  
}  
public Message(string username, string text)  
{  
Username = username;  
Text = text;  
Sent = DateTime.Now;

var dict = new Dictionary<string, string>();  
dict.Add("Text", text);  
dict.Add("Username", username);  
dict.Add("Sent", DateTime.Now.ToString());  
serializedMessage = new FormUrlEncodedContent(dict);  
}  
}  

The Message class has three simple properties - username, text and sent (DateTime). Additionally, it contains a property called “SerializedMessage”, which we’ll use to transmit the message over a POST request to the chat partner. The class has two constructors - which are two sides of the same coin, so to speak.

The first constructor is used for receiving messages (that’s why it takes the MessageReceiver instance as an argument). The second consturctor is used for building up the message that is going to be sent.

MessageReceiver class is very simple.

public class MessageReceiver  
{  
public string Text { get; set; }  
public string Username { get; set; }  
}  

We’ll use it in the incoming POST events to pull out data from the HttpRequest. This is a neat technique that Rick Strahl generously showed on his blog.

Business is bussiness, so where does all that heavy lifting happen? πŸ”—

GUI - check. Models - check. Time for Business.

Since we are building the application around ASP.NET Web API, we couldn’t do without a class inheriting from ApiController. Remember, we are only interested in handling POST events - which in our app will represent incoming messages.

public class ChatController : ApiController  
{  
public void Post(MessageReceiver simpleMessage)  
{  
MessageArrived(new Message(simpleMessage));  
}  
}  

Our ChatController’s Post method takes in the MessageReceiver instance and transforms into Message object. Additionally we need to do one more thing. We’d like the controller to notify the ChatProxy class that a message has arrived and it might be useful to display it in the GUI. How about using a static event for that?

So after modifications our ChatController looks like that.

public class ChatController : ApiController  
{  
public void Post(MessageReceiver simpleMessage)  
{  
MessageArrived(new Message(simpleMessage));  
}  
public delegate void EventHandler(object sender, MessageEventArgs args);  
public static event EventHandler ThrowMessageArrivedEvent = delegate { };  
public void MessageArrived(Message m)  
{  
ThrowMessageArrivedEvent(this, new MessageEventArgs(m));  
}  
}  

Notice that I added and Event Handler which is thrown every time an incoming message comes via a POST event. Since the event is static, we can subscribe to it without instantiating ChatController, which is crucial for us to be able to pass the event data from the POST request to the instance ChatProxy upon every call to ChatController.

Notice we used a custom EventArgs objects. It is a regular EventArgs object, extended with a property that holds a Message object.

public class MessageEventArgs : EventArgs  
{  
public MessageEventArgs(Message m)  
{  
this.Message = m;  
}  
public Message Message;  
}  

ChatProxy, where the heavy lifting happens πŸ”—

So now we, after we have covered the Message, MessageReceiver and ChatController classes, we can move on to the core part of the application, the ChatProxy class. As you recall, it is instatiated as the private property of the MainWindow class in the UI.

Let’s look at the bare-bones ChatProxy class first (without any methods).

public class ChatProxy  
{  
public bool Status { get; set; }  
public delegate void ShowReceivedMessage(Message m);  
public delegate void ShowError(string txt);  
private ShowReceivedMessage _srm;  
private ShowError _sst;  
private HttpClient _client;  
private HttpSelfHostServer _server;  
}  

We expose one public property called Status, which we’ll use in the GUI to check if things aren’t wrong. Other than that, we have:

  • 2 delegates, which - as I already mentioned - we’ll use to call the GUI methods passed in the constructor (as seen earlier). The delegates need to be public, since they’re used in the signature of the public constructor
  • an instance of HttpClient, which we’ll use for outgoing messages
  • an instance of HttpSelfHostServer, which we’ll be our self-hosted ASP.NET server, and which we’ll use to accept messages incoming via POST requests (through ChatController class)

In addition to that we will expose one more public member (already mentioned earlier) - public async void SendMessage(Message m) which we’ll call from GUI to send messages.

Finally, ChatProxy also has a few private methods:

private void StartChatServer(string myport)  
private void stopChatServer()  
private void ShowMessage(Message m)  
private void ShowError(string txt)  

The first two will control (start/stop) the chat server. By default, we’ll start the chat server in the class’ constructor. We’ll stop upon any error. ShowMessage and ShowError are just “proxy” methods to call the delegates passed from the GUI layer to the Business layer.

Constructor in all its glory πŸ”—

Let’s have a look at the ChatProxy constructor:

//constructor  
public ChatProxy(ShowReceivedMessage srm, ShowError sst, string myport, string partneraddress)  
{  
StartChatServer(myport);  
if (Status)  
{  
_srm = srm;  
_sst = sst;  
_client = new HttpClient() { BaseAddress = new Uri(partneraddress) };  
ChatController.ThrowMessageArrivedEvent += (sender, args) => { ShowMessage(args.Message); };  
}  
}  

The following happens here:

  1. First of all, we’ll try to start the local self-host server. We’ll look into that code in a second, for now we’ll just say that if it happens successfully, it would update the Status property to true, otherwise it would set that to false. If the server isnt running, there is no point in continuing with anything, so we wrap the rest of the code in an “if” clause.
  2. We then take the two methods passed from the GUI layer and assign to our Business layer delegates. This pattern gives us a possibility to update GUI from business layer without really knowing what the GUI methods do.
    Additionally, we take the partner’s address and instantiate HttpClient using that. This is now ready and waiting to send messages. Finally, we subscribe to the static Event Handler of the ChatController, to listen to events of incoming messages. This way we are almost ready with our two way communication.

Starting the server in your application πŸ”—

Let’s rewind back a bit, and look into the private void StartChatServer(string myport) method.

private void StartChatServer(string myport)  
{  
try  
{  
string url = "http://localhost:" + myport + "/";  
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(url);  
config.Routes.MapHttpRoute(  
name: "DefaultApi",  
routeTemplate: "api/{controller}/{id}",  
defaults: new { id = RouteParameter.Optional }  
);  
_server = new HttpSelfHostServer(config);  
_server.OpenAsync().Wait();  
Status = true;  
}  
catch (Exception e)  
{  
Status = false;  
ShowError("Something happened!");  
}  
}  

This is probably a familiar bit of code to you, if you have read anything about the self hosting ASP.NET Web Api. Firstly we create an instance of HttpSelfHostConfiguration - through which we set our server’s url (localhost) and port, as well as define routing; for our purposes the default routing to controller’s name will suffice.

Since our controller’s name is Chatcontroller, we’ll be posting to “api/chat” whenever we send messages. Then, all that’s left is to instantiate HttpSelfHostServer and start it. We update the status property according to the exceptions (or lack thereof) and we’re ready.

For the record, let’s show here the stopChatServer() method, which is very simple

private void stopChatServer()  
{  
_server.CloseAsync().Wait();  
}  

We’ll call it any time there is trouble.

Sending and receiving messages like a busy bee πŸ”—

Time to glue in the last piece of our puzzle - sending the message. This is surprisingly easy. Recall that by now, we have our self-hosted server up and running (so we are ready to accept incoming messages), and we have an instance of HttpClient(), pointing at our chat partner IP. So by now, all that’s left is to write a method to send the message.

public async void SendMessage(Message m)  
{  
try  
{  
HttpResponseMessage response = await _client.PostAsync("api/chat", m.serializedMessage);  
if (response.StatusCode != System.Net.HttpStatusCode.OK)  
ShowError("Partner responded, but awkwardly! Better hide!");  
ShowMessage(m);  
}  
catch (Exception e)  
{  
stopChatServer();  
ShowError("Partner unreachable. Closing your server!");  
}  
}  

Let me reiterate, why that the method is public - it’s being invoked only from the outside - in this case from the GUI layer, on the ChatProxy instance. The sending of the message is super simple, all we need to do a Post to the partner’s IP and his “api/chat” (ChatController). To do that, we call PostAsync method of the instance of HttpClient poiting to our partner, and pass the serializedMessage (form encoded). Note, that the serializedMessage property already contains properly encoded message as we populate it in the Message constructor.

If we catch some exceptions (i.e. network timeout) we close our chat server and display an error. Also, if the response code is different than 200, we’ll update the GUI with an error message.

Notice that we also call the method responsible for displaying the message in the GUI. We do that, cause in the chat area we want to see our messages as well, not just incoming ones.

Remember - we show messages and errors by invoking the delegates passed from GUI layer:

private void ShowMessage(Message m)  
{  
_srm(m);  
}  
private void ShowError(string txt)  
{  
_sst(txt);  
}  

That’s practically the entire functionality in place now.

Events flow πŸ”—

Since our discussion so far may have not been very coherent or consistent (sorry about that folks), let’s recap what we have done. Consider the entire chain of events, from the start of the application:

  1. The application opens. Self host server is not running yet.

  2. User fills in username, his port and the partner’s IP and port. Clicks the “start chat” button.

  3. The click instantiates ChatProxy in the MainWindow class. The methods responsible for UI updating - showing message and showing error are passed as delegates to the ChatProxy. ChatProxy will use them to update UI instead of calling methods of GUI namespace directly, without really knowing them.

  4. Upon instantiating ChatProxy, the self-host server gets started. Additionally, ChatProxy instance creates a private instance of HttpClient which points at the partner’s IP, and ChatProxy instance subscribes to ChatController’s MessageArrived event which will be thrown when incoming message arrives by a POST request.
    In other words the mechanisms for “send” and “receive” are on stand by.

  5. Whenever user types in a message and either clicks on send or presses enter key, the appropriate event fires and calls the only public method of the ChatProxy instance - sendMessage.

  6. Message gets posted via HttpClient to the partner. Message shows up in the sender’s GUI.

  7. Partner’s (receiver’s) ChatController receives the POST event, and emits MessageArrived event. Partner’s ChatProxy instance is subscribed to that and calls the delegate responsible for showing the message in the GUI. By that time, message is visible in the partner’s GUI as well.

That’s about it. We have our chat! By the way, if you are interested in creating chat with WCF instead, here is an excellent tutorial.

Pre-requisites and some info πŸ”—

The application requires .NET 4.5 since it uses async modifier on the method used to post messages. This can be modified though and run on .NET 4.0 as well. If you try to run the chat over the network you need to allow it through your firewall. I tested with Windows Firewall switched off, and it works like a charm. Finally, chat application needs to be run with elevation (in administrator’s mode) since HttpListener, which is used by self-host server requires such.

So how does it look πŸ”—

1. Chatting with yourself
You configure the app to use port A and point it to localhost:A as partner’s IP. This way you’d be able to chat with yourself in one window - because you’d be posting messages to your own ChatController!

2. Chatting on same machine via different ports
You can also start two separate instances of chat application. Just make one instance use port A, and point it to partner at localhost:B, while making the other one use port B and point it to partner at localhost:A.

3. Chatting over the network
You can also chat over the network (remember - firewall!). See the screenshot below:

Summary & source files πŸ”—

If you read the post closely enough, you probably recall myself bragging about the potential of ditching Live and Skype. Turns out that maybe people won’t be doing that soon, but I feel that we have built something nice with ASP.NET Web Api here.

Of course, there are countless ways of taking this further and enahncing it: introducing better error handling, incorporating smoother configuration, friend lists, automatic network discovery in LAN and many many others.
Anyway, I hope you had fun with this one.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP