次の方法で共有


Automatic camel casing of properties with SignalR hubs

UPDATE: The code in this post relates to SignalR v1. For an updated version that works for SignalR v2 check out this post.

I’ve been doing some work with SignalR recently. If you’ve not encountered SignalR before then take a look at the project wiki or Scott Hanselman’s post to find out more.

By default, SignalR preserves the case of property names when sending objects from the server to the client. My preference is for PascalCase on the server and camelCase on the client, and in this post I’ll show how you can achieve that.

Basic functionality

To avoid breaking what seems to be a tradition for SignalR posts I’ll start with a (very) basic chat-style application. I’m going to start with pretty much the same code as the hubs version of Scott Hanselman’s post. I’m not going to spend much time breaking down the initial code so if you want more explanation then check out Scott’s post.

In a standard ASP.NET Web Application project I added SignalR and modified Default.aspx to update the body content to:

<script src="Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
< script src="Scripts/jquery.signalR-0.5.3.min.js"></script>
<script src="/signalr/hubs" type="text/javascript"></script>
< script type="text/javascript">
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;

// Declare a function on the chat hub so the server can invoke it
chat.addMessage = function (message) {
$('#messages').append($('<li>').text(message));
};

$("#broadcast").click(function () {
// Handle button click and call the chat method on the server
chat.send($('#msg').val());
});

// Start the connection
$.connection.hub.start();
});
</script>
<h2>SignalR camelCasing</h2>
<input type="text" id="msg"/>
<input type="button" id="broadcast" value="Send" />
< ul id="messages"></ul>

Then I created a very simple Hubs\Chat class:

public class Chat : Hub
{
public void Send(string message)
{
Clients.addMessage("SENT: " + message);
}
}

This code creates a simple chat application. When the user clicks on the “send” button, the text from the “msg” input is sent to the SignalR Chat hub which then echoes it to all clients via the addMessage call. Clients register for this call and append the message to the “messages” list.

Aside from the functionality, there are a couple of things to point out here. Firstly, the hub is called “Chat” in the server-side code, but we refer to $.connection.chat to access it client-side. I use PascalCase server-side and camelCase client-side, so this all feels good to me. Simlarly, the hub has a “Send” method, and it is called from the client using “chat.send”.

Then we get to the call from the server to the client, and we use “Clients.addMessage” on the server. To me this doesn’t feel quite right. But it’s not all bad as I can change this to “Clients.AddMessage” on the server and SignalR still allows the client to register with the “chat.addMessage” event. So far, so good!

Adding additional data as parameters

The next step is to send some more data – suppose we want to send the timestamp for when the server received the message (or the originating user…).

One way to do this is to pass additional parameters, e.g. on the server:

public void Send(string message)
{
Clients.AddMessage("SENT: " + message , DateTime.UtcNow);
}

And on the client:

chat.addMessage = function (message , timestamp) {
$('#messages').append($('<li>').text(timestamp + ' ' + message));
};

This all works, but sometimes it makes more sense to group related data into an object.

Adding additional data as objects

Let’s suppose that we have a Message type that we want to use to encapsulate the message properties on the server:

public class Message
{
public string Body { get; set; }
public DateTime Timestamp { get; set; }
}

We could use this type on the server:

public void Send(string message)
{
Clients.AddMessage(new Message { Body = "SENT: " + message, Timestamp = DateTime.UtcNow });
}

And then consume it on the client:

chat.addMessage = function (message) {
$('#messages').append($('<li>').text(message.Timestamp + ' ' + message.Body));
};

This all works, but note how we’ve had to use “message.Body” with PascalCase on the client? If we try to use “message.body” we simply get “undefined” as there is JavaScript is case-sensitive and there is no “body” property.

Fixing the casing

As we’ve seen, for the most part SignalR is quite forgiving about casing, but when serialising objects it has to pick an option. Unfortunately (for me) it serialises the properties with the original server case. All is not lost - SignalR builds on top of JSON.NET, which has a lot of extensibility points for serialisation.

For example, we can use the JsonProperty attribute to override the name of the property when it is serialised to JSON. This can be applied to the Message class as shown:

public class Message
{
[JsonProperty("body")]
public string Body { get; set; }

[JsonProperty("timestamp")] public DateTime Timestamp { get; set; }
}

With this change, we can use the PascalCase form on the server (i.e. “message.Body”), and have it serialised into camelCase for consumption on the client (i.e. “message.body”)

Fixing the casing – take 2

The JsonProperty approach works (i.e. I can use the ‘correct’ casing in each context), but I’m lazy – I don’t want to have to mechanically go through and add the JsonProperty attribute to each object I return via SignalR. Fortunately, JSON.NET allows us to plug in custom “contract resolvers” and actually ships with a CamelCasePropertyNamesContractResolver which automatically performs the camelCase conversion for you. Sadly, using this contract resolver breaks SignalR as it affects the case for properties on all serialised objects and SignalR expects PascalCased objects on the client. Instead, we can create a custom contract resolver that only performs the case conversion for a filtered subset of types. The code for this contract resolver is in the download (link at the end of the article), but we can configure SignalR to use it by adding the following code in Application_Start in global.asax

var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new FilteredCamelCasePropertyNamesContractResolver
{
TypesToInclude =
{
typeof(Hubs.Message),
}
}
};
var jsonNetSerializer = new JsonNetSerializer(serializerSettings);
GlobalHost.DependencyResolver.Register(typeof(IJsonSerializer), () => jsonNetSerializer);

With this configuration in place we can remove the JsonProperty attributes from the Message class as we’ve registered the Message class with the contract resolver so it will automatically add this behaviour for us. We can add additional types to the collection initializer, or use the AssembliesToInclude collection

             AssembliesToInclude =
{
typeof(Hubs.Message).Assembly,
}

The code above automatically applies the case-conversion behaviour to all types in the same assembly as the Message class (and we can specify multiple assemblies if desired).

Summary

In this post we saw that SignalR allows us to use PascalCase on the server and camelCase on the client in most cases. When we’re passing objects from the server the objects are serialised into JSON using the exact property names. We saw how we can use the JsonProperty to control this for individual properties on our classes, and we then looked at how we can create a contract resolver to instruct JSON.NET to convert the property names on serialisation.

The accompanying code can be downloaded from: https://code.msdn.microsoft.com/Automatic-camel-casing-of-648c8883

Comments

  • Anonymous
    September 13, 2012
    The comment has been removed

  • Anonymous
    September 13, 2012
    The comment has been removed

  • Anonymous
    September 18, 2012
    Hi, This is awesome! I just ran in to this requirement myself and you've saved me a couple of hours (at least). Thanks for putting this up.  :) -- Ragesh.

  • Anonymous
    September 18, 2012
    Hi Ragesh, Glad to hear it helped!

  • Stuart
  • Anonymous
    September 27, 2012
    Thanks! Saved me a ton of time.