6
\$\begingroup\$

I am writing an application that uses ASP.NET MVC for its front end, and for the back end, it uses RavenDB to store data. This has so far worked out great, but I am hitting a huge wall with the use of enum in selecting things.

The problem comes with front end interaction. I have many places where I want enums to be selectable via drop down lists and the like. This works okay on the surface, but I ran across an issue with the actual relationship between enums and JSON as a language/format. These issues were not overcome by using the traditional [JsonConverter(typeof(StringEnumConverter))] attribute in my models.

Essentially put, MSDN reports the following; from the MSDN

Enumeration member values are treated as numbers in JSON, which is different from how they are treated in data contracts, where they are included as member names. For more information about the data contract treatment, see Enumeration Types in Data Contracts.

For example, if you have public enum Color {red, green, blue, yellow, pink}, serializing yellow produces the number 3 and not the string "yellow".

All enum members are serializable. The EnumMemberAttribute and the NonSerializedAttribute attributes are ignored if used.

It is possible to deserialize a nonexistent enum value - for example, the value 87 can be deserialized into the previous Color enum even though there is no corresponding color name defined.

A flags enum is not special and is treated the same as any other enum.

Now what this meant is that even if I used fancy footwork to convert my enums into strings for my dropdownlists, they still get saved back to the database as integers; Or rather it is more sufficient to say that they get saved in Raven as strings, but still 'considered' as integers upon deserialization back to the javascript components/MVC.

Using a lot of time and patience, I did manage to find "work arounds" for this; They were cumbersome and obtuse, I did not like them. It made plugging my enums into various UI javascript packages difficult. In the end, I elected to just do away with enums entirely and go with a different approach.

So what I did is create a base class Listable.

public class Listable : IHasIdentity, IHasName, IHasLabel, IHasStyle {

    public Listable(): base() { }

    public string Id {  get;  set; }
    public string Name { get; set; }
    public string Label { get; set; }
    public string CSS { get; set; }
    public string Description { get; set; }
    public int Order { get; set; }
    public string Keyword { get; set; }
}

Listable is used to form a collective association within my program, so there is another base class known as a Listing.

[RavenTag("listings")]
public class Listing : IHasIdentity, IHasName, IMayTransform, IRavenIdentity {
    public Listing(): base() {
        Items = new List<Listable>();
    }

    public string Id { get; set; }
    public string Name { get; set; }
    public List<Listable> Items { get; set; }

    /// <summary>
    /// The identity for raven to generate for this document when it is created.
    /// </summary>
    /// <returns>
    /// The complete identity for Raven to use for this object.
    /// </returns>
    public string Identity() {
        return string.Format("listings/{0}", Name.AsIdentity());
    }
}

So then, a Listing has many items, and upon being created it uses an extension method to save an identity for each item in the listing, defined here;

public static class TypeExtensions {
    public static Models.Listing ToIdentities(this Models.Listing obj) {
        // set the identity of each item in the collection
        foreach (var item in obj.Items)
            item.Id = string.Format("list/{0}/{1}",
                obj.Name.ToLower().Replace(" ", "-").Replace("/", "-"),
                item.Name.ToLower().Replace(" ", "-").Replace("/", "-"));
        return obj;
    }
}

This seems like a lot of work just to get a simple list, but in the end it paid off because now I have a user interface where my staff members can define any number of lists, with any number of items, and edit them; And then these can be easily bound to javascript controls in any way we need like standard objects. An example of such a list is as follows;

{
  "Name": "Genders",
  "Items": [
    {
      "Id": "list/genders/male",
      "Name": "Male",
      "Label": "Male",
      "CSS": null,
      "Description": null,
      "Order": 0,
      "Keyword": null
    },
    {
      "Id": "list/genders/female",
      "Name": "Female",
      "Label": "Female",
      "CSS": null,
      "Description": null,
      "Order": 1,
      "Keyword": null
    },
    {
      "Id": "list/genders/none",
      "Name": "None",
      "Label": "",
      "CSS": null,
      "Description": null,
      "Order": 2,
      "Keyword": null
    }
  ]
}

And then these are easy to query, a simple service on my controllers makes it trivial.

[HttpGet]
[Route("list/named/{name}")]
public JsonResult Named(string name) {
    // query for all known list items
    var listable = RavenSession
        .Load<Listing>(String.Format("listings/{0}", name))
        .Items
        .OrderBy(n => n.Order)
        .ToList();

    return Json(listable, JsonRequestBehavior.AllowGet);
}

So with this, I can plug this into anything that expects JSON data, for example in my situation, I am using Kendo UI and their DropDownList component with RemoteDataSource, it is easy to make drop down lists bound to simple objects.

$("#genders").kendoDropDownList({
    dataTextField: "Name",
    dataValueField: "Id",
    optionLabel: "Select ... ",
    dataSource: {
        dataType: "json",
        transport: {
            read: {
                url: "/list/named/genders"
            }
        }
    }
});

Because I am dealing with simple JSON, and not Enums, this does everything I want. It gives me infinite list flexibility, it allows more verbose data that I may want in the future, and it provides a convenient model for repeating the process in multiple places.

The Question

I recently came under fire by a co-worker who saw this and claimed that it was "bad practice", and I should just use enums and deal with the problems of converting them all the time. To me, that seems troublesome and like it leaves so many gaps, and causes so many issues and leaves it so confusing. But their reasoning is that because enums are so much smaller in size, it will improve performance.

So I wanted to get the opinions of more people who may have experienced this. There are about 37+ lists in the entire program so far, and this is running great for me, and my control panel users love the flexibility. It has also made testing a lot simpler, and upgrading simpler.

\$\endgroup\$
  • \$\begingroup\$ I'm confused, why where you using an enum for a user defined drop down list? because that's what it sounds like you're describing but an enum is not user defined. \$\endgroup\$ – Eluvatar Mar 11 '14 at 14:43
3
\$\begingroup\$

It seems like you've gone to more trouble to avoid using enum than you would have had just using them. enums are just numbers with a mask, converting to an int to an enum and vica versa is quite simple in C#, they also have a ToString which makes using their string value super simple as-well. I probably don't grasp the troubles you were having with using enums, but they are certainly more efficient than using strings. I think the helper methods you've created are great and it the implementation details of your project are certainly up to your team, and depending on the size and longevity of the project you may decide to continue using the string system. However in the future I would highly recommend using enums instead, and perhaps make similar helper methods which help with the troublesome conversions.

Short story: Your co-worker is right, enums are better, but it is ultimately up to your team on the implementation of this. If it is worth your time, change it, else use enums on your next project.


Edit: Use Json.Net to better serialize enums

https://stackoverflow.com/a/2870420/1812944

\$\endgroup\$
  • \$\begingroup\$ But it isn't possible to get enums to cooperate with the way the data needs to be displayed. They can only get recognized by JSON as integers, but they need to be strings. \$\endgroup\$ – Ciel Mar 11 '14 at 14:56
  • \$\begingroup\$ I basically had to do a ton of extra conversion to make sure things stayed correct, and then if we ever had to update the enums with new values, it destroyed all of the existing ones. \$\endgroup\$ – Ciel Mar 11 '14 at 14:59
  • \$\begingroup\$ @Ciel Do you have a database you can store your enums in, and just pull the appropriate strings in when needed. And Look at the edit I just made... It may be something that can help you out. \$\endgroup\$ – BenVlodgi Mar 11 '14 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.