So last time around we built together a small p2p chat app using WPF and ASP.NET Web API. Today, we are continuing our experiments with ASP.NET Web API and setting up a simple push (or push-like) messaging system between different apps using self hosting.
In fact, we’ll use one app (“server”) to push out messages to a number of other apps (“clients” or “subscribers”). Even though we have the (in)glorious WCF callbacks at our disposal, I thought it might be fun to try it that way. Hopefully, that sounds interesting, or at least intruiging. In the process we’ll also serialize custom types to JSON using JSON.NET and pass them between self-hosting applications.
More after the jump.
So what are we trying to do here? π
Let’s have one application that’s a controller (let’s call it “server”), and several others that are receivers (let’s call them “clients”). Of course our naming convention here isn’t exactly accurate since logically they will all be web servers running ASP.NET Web API.
Anyway:
- server will be started and running
- at any time a client can show up and subscribe to the server to receive it’s messages. Client can also leave at any time, the server should recognize that
- server will push out objects containing information to all the subscribed clients which will display that
Of course we are simplyfing things here, showing just a pattern. We could easily swap pushing simple objects with pushing complex objects and swap console app with a complex WPF app.
The Beginnings π
So we’re gonna have 3 projects - client, server and a class library for the shared entities. I called it, very creatively, ClassLibrary1. Server and client will require AspNetWebApi.SelfHost and JSON.NET packages from NuGet.
We’re gonna start off really simple, with a Message class that is going to be the object passed between the client and the server. We’ll put in into the class library - a separate DLL so that both our client can use it. We will be serializing it into JSON, because I have been getting quite a few questions about how to serialize and POST objects as JSON using ASP.NET Web API and JSON.NET.
The Message class looks like this:
namespace ClassLibrary1
{
[DataContract]
public class Message
{
[DataMember]
public string info { get; set; }
[DataMember]
public DateTime dt { get; set; }
public Message(string i, DateTime d)
{
info = i;
dt = d;
}
}
}
It has a DateTime property which we’ll use to measure the gap interval between creating the object (and sending it) and receiving it on the other end. We decorated the members with [DataMember] attributes and the class with [DataContract] attribute, so make sure the project references System.Runtime.Serialization.
Serializing objects to JSON π
The next step is to setup our JSON Serialization formatter. By default ASP.NET Web API will do it in XML. If you go for JSON serialization, it will force you to use DataContractJsonSerializer which, and those who have worked with WCF a lot would certainly agree, is terrible. So we’ll use JSON.NET, and we’ll rely on a technique shared by Henrik Nielsen here. Have a look there and grab the excellent JsonNetFormatter class.
Once we have the JsonNetFormatter class set up in our project (again, see the code by Henrik) the usage is very simple. Whenever we start our self host server, we’ll be telling ASP.NET Web API to use that formatter. See the code below.
var url = "http://localhost:9000/";
var config = new HttpSelfHostConfiguration(url);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
config.Formatters[0] = new JsonNetFormatter(serializerSettings);
var s = new HttpSelfHostServer(config);
s.OpenAsync().Wait();
So we added our custom JsonNetFormatter (in this cased based on JSON.NET), as the first entry to the Formtters property of HttpSelfHostConfiguration instance (same as used to configure the routes). The same instance is then passed to the constructor of HttpSelfHostServer.
When this is all done, the usage in requests is dead simple.
Message msg = new Message(txt, DateTime.Now);
HttpContent c = new StringContent(JsonConvert.SerializeObject(msg));
c.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage result = await client.PostAsync("api/main", c);
Whenever we want to post a T object, we call JsonConvert.SerializeObject(T), and pass the output to the constructor of a StringContent object. Then we just set the headers to be “application/json” and we can POST the object using and instance HttpClient. When we pick up this POST request, no type casting is necessary:
public class MainController : ApiController
{
public void Post(Message message)
{
//do something with Message
}
}
And the Message object has travelled through HTTP as JSON.
Stiching it all together π
So we covered the basics:
- we have a Message class which will be posted between subscriber and receiver
- we set up JSON serialization using JSON.NET
- we know how to POST using that
Let’s now build our simple apps, and we’ll use the wonderful console for that. Remember to change the target framework of the console apps to .NET 4.5.
Server a.k.a. Sender
What is it gonna do:
- on startup will start the self-host server and start “awaiting” subscribers
- will take keyboard input and upon ENTER press, will send this as MEssage object to all the subscribers
- we’ll keep track of subscribers - any time a new subscriber connects, we’ll be added to the receivers repository, if one of the goes offline it will be removed
To start off we need a subscriber class to represent our subscribers.
public class Subscriber
{
public string name { get; set; }
public Subscriber(string s)
{
name = s;
}
}
Simple enough. Then we’ll just create a simple static class to act as our in-memory repository of subscribers.
public static class AppStorage
{
public static IList<Subscriber> subscribers { get; set; }
}
We also need a Web API controller that will “listen” or accept subscribers.
public class MainController : ApiController
{
public void Post(Message msg)
{
Subscriber s = new Subscriber(msg.info);
AppStorage.subscribers.Add(s);
Console.WriteLine("Client {0} subscribed! Exec time: {1}", s.name, DateTime.Now - Convert.ToDateTime(simpleMessage.dt));
}
}
As you can see, as we discussed there is no type casting involved, as the object will auto magically deserialize itself from JSON to Message type.
Now all we need to add for the server is whatever is going to happen in runtime.
class Program
{
static void Main(string[] args)
{
var url = "http://localhost:9000/";
var config = new HttpSelfHostConfiguration(url);
AppStorage.subscribers = new List<Subscriber>();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
config.Formatters[0] = new JsonNetFormatter(serializerSettings);
var s = new HttpSelfHostServer(config);
s.OpenAsync().Wait();
Console.WriteLine("I'm up and ready to go!!!");
while (0 < 1) { PushMessages(Console.ReadLine()); }; } }
This is essentially the same bit of code as we had a look at earlier. So when we start the app, we also start the HttpSelfHostServer at port 9000. then we have a silly infinite loop (sorry couldn’t resist putting this in) which means we’ll be waiting for text input forever and take as many new messages as possible and send them to subscribers using PushMessages method. The last piece of our server is that method.
static async void PushMessages(string txt) { int subscribersCount = AppStorage.subscribers.Count; for(int i = subscribersCount-1; i >= 0; i-)
{
var client = new HttpClient() {
BaseAddress = new Uri(AppStorage.subscribers[i].name)
};
Message msg = new Message(txt, DateTime.Now);
HttpContent c = new StringContent(JsonConvert.SerializeObject(msg));
c.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage result = await client.PostAsync("api/main", c);
if (result.IsSuccessStatusCode)
Console.WriteLine("Response from {0}: {1}", AppStorage.subscribers[i].name, result.StatusCode);
else
AppStorage.subscribers.RemoveAt(i);
Console.WriteLine();
}
}
This is an async method to POST to all subscribers. Again, this is essentially the same code as I showed earlier. We iterate through subscribers - we are using a for loop and counting down, this way we can remove a subscriber which is offline - and instantiate an HttpClient poitning at each one’s IP address. then we post to the “api/main” controller, and show the response. That’s it, simple an effective “push” messaging.
Client a.k.a. Subscriber
Now let’s look at the client, cause it is going to be very similar to the serv with some slight modifications.
But what shall it do functionally?
- upon startup will ask you at which port should it listen
- when the port is given, a self-host server is started
- upn hitting ENTER client connects to the server at its port 9000 and becomes ready to receive messages
The API controller on the client side is merely going to display the messages as they arrive.
public class MainController : ApiController
{
public void Post(Message msg)
{
Console.WriteLine("Received: {0}, exec time: {1}", msg.info, DateTime.Now - msg.dt);
}
}
Notice we also don’t type cast here (relying on JSON deserialization) and we display the time needed for the Message object to arrive and be shown.
We’ll need a static AppStorage as well:
public static class AppStorage
{
public static string serverName { get; set; }
}
This way we can access the name from anywhere. This is the Main method of the applciation, very similar to the “server” setup.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Choose port");
string port = Console.ReadLine();
if (string.IsNullOrWhiteSpace(port)) port = "90";
var url = string.Empty;
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
url = "http://"+ip.ToString() + ":" + port + "/";
break;
}
}
AppStorage.serverName = url;
var config = new HttpSelfHostConfiguration(url);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
config.Formatters[0] = new JsonNetFormatter(serializerSettings);
var s = new HttpSelfHostServer(config);
s.OpenAsync().Wait();
Console.WriteLine("I'm up and ready to go!!! Press any key to subscribe to server…");
Console.ReadLine();
Subscribe();
Console.ReadLine();
}
}
We start the HttpSelfHostServer based on the port provided from the keyboard and the IP address read from the DNS host. This IP will be then passed to the server - as upon pressing enter we susbcribe to the server using Subscribe method.
static async void Subscribe()
{
var client = new HttpClient() {
BaseAddress = new Uri("http://localhost:9000")
};
Message msg = new Message(AppStorage.serverName, DateTime.Now);
HttpContent c = new StringContent(JsonConvert.SerializeObject(msg));
c.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage result = await client.PostAsync("api/main", c);
Console.WriteLine("Subscription status: {0}", result.StatusCode);
}
Again, the principle is simple, and the code should be more then familiar by now - we submit a POST to the server at port 9000, apss the Message as a serialized JSON and write out the response. If you recall, once a client is subscribed, it will be getting messages from server.
Trying it out. π
Now let’s try this out. Rememeber to run the apps - either from Visual Studio or from the EXE build, as administrator. Also, if you do stuff over the network, remember to open the appropriate ports in the firewall (or just switch off Windows firewall temporarily if you are using it).
Here’s the scenario:
- run the server window
- run 3 clients, give each a different port (if you are experimenting on local) or whatever port (if you are experimenting over the network) and hit enter to subscribe to the server
- you should see the subscribed client notification appearing in the server window
- start typing in the server window, and hit enter to see the message appearing in the different clients. You should be getting this:
Summary & Source files π
Of course this is far from anything useful at this moment. But we have shown how we can make one application “push” messages to a number of other applications via HTTP using ASP.NET Web API. Now imagine how much you can do with that - you could have a set of remotely controlled apps, all being governed from a single place in real time or anything really that you can imagine π
Also please note, that each subscriber could do different things with the arriving message. So we could have forms apps, WPF apps, console apps etc. all running along each other and receiving the same message object, and utilizing it in a different way. Neat! Anyway, till next time!
- 3 source projects - client, server & class library (20MB, zip)