Creating ASP.NET HttpCookie-compatible multi-valued cookies in JavaScript
Cookies are ubiquitous on the web. They're used to store usernames, login tokens, shopping cart contents, and so on. In libraries, cookies are typically modeled as name-value pairs. An API consumer requests a cookie using a known name (e.g. $.cookie('user')
in jQuery with the Cookie plugin) and the library returns a string (e.g. alice
). Sometimes, however, just plain old name-value pairs aren't enough. In this article, we will take a look at how to bake multi-valued cookies in JavaScript that are compatible with the Values property of ASP.NET's HttpCookie class.
Serialization
The .NET HttpCookie class stores multiple values in a single cookie by serializing them into a single string. For example, this code:
HttpCookie cookie = new HttpCookie("Cookie1");
cookie.Values["Val1"] = "1";
cookie.Values["Val2"] = "2";
cookie.Values["Val3"] = "3";
Response.Cookies.Add(cookie);
...will result in a cookie that looks like this:
Cookie1=Val1=1&Val2=2&Val3=3
Thus, we can see the HttpCookie serializes the Values property in a similar way to how parameters are encoded in query strings.
The JavaScript (for MS .NET and Mono)
Based on the example output Cookie1=Val1=1&Val2=2&Val3=3
, we can work out some JavaScript code to generate it:
// Works with Microsoft .NET and Mono runtimes, with reservations.
var setMultiValuedCookie = function(name, values) {
var valuePairs = [];
for (var n in values) {
valuePairs.push(n + "=" + values[n]);
}
var cookieValue = valuePairs.join("&");
document.cookie = name + "=" + cookieValue;
};
This is more or less how the Microsoft .NET implementation of HttpCookie works. It also happens to be compatible with the Mono implementation (more on that below). The problem with this code is that it does not take into account edge cases, such as when a name or value contain the "=" or "&" characters. In those cases, things can get weird. For example, consider this code:
HttpCookie cookie = new HttpCookie("Cookie1");
cookie.Values["Val1"] = "1&Val2=2";
cookie.Values["Val3"] = "3";
Response.Cookies.Add(cookie);
Notice how only two values are set. Yet, when encoded by Microsoft's HttpCookie class the resulting output is Cookie1=Val1=1&Val2=2&Val3=3
, the exact same output that results the previous three value example in article. However, the cookie, when parsed by HttpCookie, will always be interpreted as having 3 values. This means that Microsoft's .NET HttpCookie serialization scheme is ambiguous and care must be taken when using it with non-alphanumeric characters.
The JavaScript (just for Mono)
The Mono Project's HttpCookie class fares a lot better than its Microsoft counterpart. In the HttpCookie source, there is a nested CookieNVC class (NVC stands for NameValueCollection) with the serialization code in the overridden ToString method:
public override string ToString() {
StringBuilder builder = new StringBuilder("");
bool first_key = true;
foreach (string key in Keys) {
if (!first_key) {
builder.Append("&");
}
string[] vals = GetValues(key);
if (vals == null) {
vals = new string[1] {String.Empty};
}
bool first_val = true;
foreach (string v in vals) {
if (!first_val) {
builder.Append ("&");
}
if (key != null && key.Length > 0) {
builder.Append (HttpUtility.UrlEncode(key));
builder.Append ("=");
}
if (v != null && v.Length > 0) {
builder.Append (HttpUtility.UrlEncode(v));
}
first_val = false;
}
first_key = false;
}
return builder.ToString();
}
Notice that in the Mono HttpCookie, the names and values are URL encoded in order to escape the "=" and "&" characters. This means that we can use "=" and "&" characters in the name and value of the Values property and they will be serialized unambiguously. To make our JavaScript work with the Mono HttpCookie class improvements, the name and value strings must be URL encoded using encodeURIComponent
:
// Warning: Does not work well with the Microsoft .NET runtime.
var setMonoMultiValuedCookie = function(name, values) {
var valuePairs = [];
for (var n in values) {
valuePairs.push(encodeURIComponent(n) + "=" + encodeURIComponent(values[n]));
}
var cookieValue = valuePairs.join("&");
document.cookie = name + "=" + cookieValue;
};
And presto, we have a Mono-compatible verison of setMultiValuedCookie
.
Conclusion
In this article we examined how the Microsoft and Mono versions HttpCookie class serialize multiple values into a single cookie. By looking at a sample cookies encoded by HttpCookie, as well as perusing the Mono HttpCookie source code, two JavaScript functions were written: setMultiValuedCookie
, a function that serializes multi-valued cookies in manner compatible with both Microsoft and Mono .NET runtimes and setMonoMultiValuedCookie
, a function that more robustly serializes multi-valued cookies but is only compatible with the Mono .NET runtime.
2 comments