I would love for the people implementing my API to send me a value from my enumerator as an input parameter. But, I’d also love it if they could somehow access all possible values of that enumerator in some other way other than reading the documentation (because, let’s be honest, who reads the documentation anyway, right?).
While we could go into the whole “don’t use enums, use lookup tables or static strings….” let’s not go there in this post. In this post I will explain how I dealt with the problem of returning all possible values from an enum
type with the integer key and a descriptive name that could be shown to the users.
The code to follow along with the blog post is available on Github.
So, I have an enum
:
public enum DogTypesEnum { GERMAN_SHEPHERD = 1, LABRADOR_RETREIVER = 2, SIBERIAN_HUSKEY = 3 }
I would like to return a nice JSON object from my API looking something like this:
[ { 'key': 1, 'name': 'German shepherd' }, { 'key': 2, 'name': 'Labrador retriever' }, { 'key': 3, 'name': 'Siberian huskey' } ]
However, I have several different enum
types in my code, so I’d like to do this as universally as possible.
So the first thing we need to do is to get all possible values from an enum
:
var allPossibleValues = Enum.GetValues(typeof(DogTypeEnum)).Cast<DogTypeEnum>()
OK – that was not too hard. Now we need to make this universal – I’d love to be able to get it for any enum
type. This is where the trickery starts. You cannot extend System.Enum
to add your own extension methods.
But, we can make a static class like this one:
public class Enum<T> where T : Enum { }
Do note that this is not actually extending the System.Enum
class, but adding a completely new class of type Enum<T>
. Technically it could be called EnumHelper<T>
or something to make things clearer. This is an idea I found on StackOverflow (thanks to ShawnFeatherly).
So, now that we have this, we can just add our method that gets all possible types in there:
public class Enum<T> where T : Enum { public static IEnumerable<T> GetAllValuesAsIEnumerable() { return Enum.GetValues(typeof(T)).Cast<T>(); } }
Even though, as we already mentioned, this is not a true extension of the System.Enum
, it is close and does the trick quite nicely.
Now I can do something like:
var allPossibleValues = Enum<DogTypeEnum>.GetAllValuesAsIEnumerable();
I have a universal DTO for any enum type:
public class EnumDTO { public int Key { get { return Convert.ToInt32(_enum); } } public string Name { get { return _enum.ToString(); } } private Enum _enum; public EnumDTO(Enum inputEnum) { _enum = inputEnum; } }
I like to keep a reference to the initial enum from which this DTO was created – not that it matters, but if I ever need it in the future, I can always extend this to get it and use it.
So, to get an actual list that can be returned via an API we can do this:
var enumDTOs = Enum<DogTypeEnum>.GetAllValuesAsIEnumerable().Select(d => new EnumDTO(d));
By returning enumDTOs
from the API controller we would get an output similar to what was initially intended. However, there is one single problem. The value of our Name
property is “GERMAN_SHEPHERD”, and we’d want it to be something more user friendly, like e.g. “German shepherd”.
As I said, I’d love it if this could be universal. So I’d love it if I could manipulate the ToString()
method of System.Enum
. However, as I already mentioned, this is not possible.
So, a possible approach is to annotate your enum values with an attribute that can then hold the desired value and then just use that. It is possible to create your own, but I am using the Description
attribute that already exists in the System.ComponentModel
namespace.
public enum DogTypesEnum { [Description("German shepherd")] GERMAN_SHEPHERD = 1, [Description("Labrador retreiver")] LABRADOR_RETREIVER = 2, [Description("Siberian huskey")] SIBERIAN_HUSKEY = 3 }
We then just need a method that will then read this Description
parameter when we need it. We cannot change the ToString()
method of Enum
, unfortunately. But, we can extend any Enum
with a ToDescription()
method that would then return this description:
public static class AttributeHelperExtension { public static string ToDescription(this Enum value) { var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false); return da.Length > 0 ? da[0].Description : value.ToString(); } }
Now we can do something like this:
var dogType = DogTypeEnum.GERMAN_SHEPHERD; var dogTypeName = dogType.ToDescription(); // value will be "German shepherd"
After this, we can just change the definition of the Name
property in the EnumDTO
class:
public string Name { get { return _enum.ToDescription(); } }
After implementing this last change our output JSON looks indeed like we intended it to.
Are Enums a good thing to use? Should all of this been a static class with properties? Should all of this been a lookup table in the database? All of these are valid points and deserve a discussion over a few rounds of beer. (Incidentally, if you like discussions like these, please contact me so we can have a few rounds of beer).
But, in different codebases you’ll still find enums. And the case described here is not an unusual one. A lot of specifics of this particular case are discussed in length on StackOverflow and other developer sites. However, I did feel a need to show my take on the whole process, top to bottom.
You are currently offline
Hey Mario, it seems you missed the part about API. Would be great to add example of the controller with actions: GET enum as int/string/description. Also would be great to see the route constraint for enum when you have smth like GET Route(“products/{category:myEnum}”)]
Dmitry, thanks for your comment! And thanks for the message later where you provide additional resources about your question đ
So, there is a great article by Nick Heath on his blog about adding Enums as route parameters:
https://nickheath.net/2019/02/20/asp-net-core-enum-route-constraints
Dmitry, you’re really awesome for sending this over, it is a great read!
I might be incorporating some of these tips into my article as well at some point, but for now this should do đ