Coming from a WCF world, I frequently used a contract-sharing trick that allowed both client and server to use the same set of DataContracts. This was especially important when we starting using enumerations to better describe what certain values meant because the client and server could now both inherently have the same meaning for the same value.
Of course, Web API is not the same beast as WCF, and we cannot have anything resembling a "contract" to share between client and server. Without that shared definition, how can we handle enum values in our Web API calls in such a way as to preserve meaning? The answer is easier than you probably think it is.
Background
First off, you need to know that as far as Web API is concerned, enumerations are not special. They're just values of some kind (usually integers) that can be serialized like anything else. The only reason enumerations have meaning of any kind is because programmers impart meaning upon them.
What this means is that Web API has no special handling in place for enumerations. Instead, it expects the programmers to handle them however they need to, and in this post that's exactly what we're going to do. Let's find out how to serialize enumerations in ASP.NET Web API!
Serialize Meaning, Not Value
Imagine that we have the following enumeration:
public enum Language
{
English,
French,
Spanish,
Russian,
German,
Mandarin,
Cantonese,
Farsi,
Zulu
}
We might also have the following class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Language PrimaryLanguage { get; set; }
}
Finally, we could have an action in our ValuesController that looks like this:
[Route("Person")]
[HttpGet]
public Person Get()
{
var person = new Person()
{
FirstName = "John",
LastName = "Smith",
PrimaryLanguage = Language.English
};
return person;
}
If we hit this action (/values/person) in a browser, what is the resulting JSON response?
{"FirstName":"John","LastName":"Smith","PrimaryLanguage":0}
Well, we got a person back, sure enough. But imagine that if we are the client, and we make this call, what does 0 actually signify? "0" has no inherent meaning; it's just a magic number that could mean, well, anything really. So how do we get the meaning represented by this value?
What we can do is return the name of the enumeration rather than the value. Assuming our enums are named properly and descriptively (which could be a big assumption), this allows us to fight ambiguity and relay meaning to the client.
Web API uses JSON.net natively, so we can take advantage of the JSON.net converters. One such converter is StringEnumConverter, which happens to do exactly what we want; namely, take an enumeration and serialize it to its name, not its value. There are three possible ways we can tell JSON.net to serialize an enum to its name; the difference between them is what kind of scope we wish to use.
Solution #1: Decorate the Class Property
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public Language PrimaryLanguage { get; set; }
}
This notifies JSON.net to only serialize this property to the enumeration name. This solution has the smallest scope, as only this particular property of this particular class we be serialized to the enumeration name rather than the value. What if we want to do this for all instances of a single enumeration? The second solution will do that for us.
Solution #2: Decorate the Enumeration
[JsonConverter(typeof(StringEnumConverter))]
public enum Language
{
English,
French,
Spanish,
Russian,
German,
Mandarin,
Cantonese,
Farsi,
Zulu
}
This commands JSON.net to serialize all instances of this enumeration to their name; anytime this enum is serialized, you'll values English and Farsi instead of 0 and 7.
However, what if we always want the name serialized rather than the value? That's where the third solution comes in handy.
Solution #3: Add the Converter Globally
The third solution makes all enumerations serialize to their name wherever they are used. We would do this by modifying the WebApiConfig file:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
// Web API routes
}
Now, any time the converter encounters an enumeration that needs to be serialized, it will do so using that enumeration's name rather than its value.
POSTing an Enum by Name
The three solutions above nicely handle scenarios in which we are returning enumerations to the client, but what about situations where the client wants submit an enumeration value to the server? Of course, they could submit such a value like so:
/values/sendLanguage/2
But, again, there's no meaning attached to the value 2. If the users wanted to use a URL that used the name of the language, how would we handle that?
/values/sendLanguage/English
All we would need to do is use Enum.TryParse()
like so:
[Route("SendLanguage/{language}")]
[HttpPost]
public Language? SendLanguage(string language)
{
Language parsedLanguage;
if(Enum.TryParse<Language>(language, true, out parsedLanguage))
{
return parsedLanguage;
}
return null; //error, not a valid language
}
(Note that the boolean true
in the TryParse call makes the parsing case-insensitive).
I imagine that this kind of thing would be the most useful in searching or filtering scenarios, where the client wants to find all people in a database that speak English or something similar. With this type of solution (and good naming guidelines) it should be simple to determine what the value of an enumeration is without resorting to the use of magic numbers.
These are all the rules I've come up with for handling and serializing enumerations in ASP.NET Web API. Check out the sample project on GitHub; it has all of these demos included.
Do you have any guidelines for handling enumerations in Web API that I might have missed? Let me know in the comments!
Happy Coding!