41

I have a RESTful Web API project, and I have 2 different Enum scenarios that I'm unsure of re best practice.

Scenario 1 : Straightforward Enum Param

My API method requires a parameter called ruleType, with valid values being EmailAddress and IPAddress. My enum within the Web API project looks like this:

public enum RuleType
{
    None = 0,
    EmailAddress = 1,
    IPAddress = 2
}

My question for this scenario is, should I use ?ruleType=EmailAddress in my request to the API (which automatically binds that value to my RuleType property within the API method)? If so, how best to validate that the RuleType param sent, is a valid RuleType Enum value?

Scenario 2 : Multiple Enum Values for a Single Param

My API method has an optional fields param, which is allows you to specify any additional data that should be returned. E.g. &fields=ruleOwner,rule. This would return those 2 extra bits of data in the response.

I have an enum in the Web API project which relates to each possible field that may be requested, and at present, I am splitting the comma separated fields param, then looping through each string representation of that enum, parsing it to the equivalent enum, resulting in a list of Enum values which I can then use within my API to retrieve the relevant data.

This is the Enum:

public enum OptionalField
{
    None = 0,
    RuleOwner = 1,
    Rule = 2,
    etc.
}

What would be best practice here? I was looking into bitwise enums, so a single value is sent in the API request which resulted in any combination of fields but didn't know if this would work well with a Web API, or if there's generally a better way to go about this?

marcusstarnes
  • 6,393
  • 14
  • 65
  • 112

5 Answers5

56

The simplest answer is, "It doesn't matter".

If the parameter in your controller method is of the enumeration type

public IHttpActionResult Foo(RuleType ruleType)

In WebAPI, It Just Works - no matter if the client request URL specifies the parmeter value as ?ruleType=1 or ?ruleType=EmailAddress

If the client specifies a value that isn't valid for the enumeration, an exception is thrown (The parameters dictionary contains a null entry for parameter 'ruleType' of non-nullable type 'RuleType' for method 'Foo' ... and the client gets a 400 Bad Request response.

Mr. T
  • 3,892
  • 3
  • 29
  • 48
  • 17
    I love those ASP.NET Core related *It Just Works* answers ^_^ – ˈvɔlə Sep 15 '18 at 23:59
  • 1
    It would be nice if the swagger documentation also describes the possible values for this query param as strings; by default it seems to use integers. How can this be changed ? – Frederik Gheysels Feb 21 '20 at 13:55
  • 3
    @FrederikGheysels It's a Swagger configuration option. See https://stackoverflow.com/questions/36452468/swagger-ui-web-api-documentation-present-enums-as-strings – Mr. T Feb 21 '20 at 17:20
  • ?ruleType=9999 also works, yay! – Salman A Aug 01 '22 at 12:41
  • It seems that ASP.NET throws an exception if the parameter integer is not a valid number for that enum, but if the client does not specify the parameter at all (I misspelt the parameter's name), ASP.NET does not return "bad request", but just passes 0 for that enum, even if the enum has no 0 (e.g.,` enum Animal{Dog=1}`). I wonder why ASP.NET works this way. – Damn Vegetables Dec 30 '22 at 17:22
11

it is a best practice to make the URI "human readable". so i can also understand why you using Enum as a string. But as HristoKolev said you have to write a custom Model Binder.

In fields i think you should not use an combination of enums. because it is difficult to understand. perhaps you can create an combination of enum as enum entry

public enum OptionalField
{
    None = 0,
    RuleOwner = 1,
    Rule = 2,
    RuleAndRuleOwner = 3,
    etc.
}
6

For scenario 2 there is built in support in C# for Bitmask operations in Enums using the [Flags] attribute

[Flags]
public enum OptionalField
{
    None = 0,
    RuleOwner = 1,
    Rule = 2,
    RuleAdministrator = 4,
    RuleEditor = 8,
    ...etc
}

Which is described in this SO post

As Christian already stated in his answer it's probably not good practice to use this in a REST API but it should work.

Chris W
  • 231
  • 3
  • 6
  • 3
    The `[Flags]` attribute has nothing to do with the bitmask operations. It is just a syntactic hint for the compiler to resolve the names of masked values as `NameOne | NameTwo`. Bitwise operations are naturally supported though, given you've made the effort yourself to set the enum values to powers of two. – Bozhidar Stoyneff Oct 29 '18 at 16:41
3

Multiple values or not, if you are using an Enum as a string, you have to parse it manually. In .NET the Enums are integers so if you want to send an enum to the default model binder you have to do it like this: ?ruleType=1.

You can write your own model binder that will accept string, but you have to ask yourself why are we doing it? If you want the user to be able to easily identify the url then use strings. If not there is no reason not to use integers. As you said, you can use FlagsAttribute to combine multiple values.

Peter B
  • 22,460
  • 5
  • 32
  • 69
Hristo Kolev
  • 1,486
  • 1
  • 16
  • 33
  • 11
    There is a good reason to use strings instead of numbers, though: The numbers are an internal implementation detail, whereas the API is a public front-end for the application. There are legitimate reasons to want to reorder the members in an `enum`, but by default doing so will change the numeric values -- but not the names. Internally to the code, the numeric values should not make a difference, because everything references the same `enum` definition. External callers do not get that benefit, and should use the string form to ensure consistency across updates to the service code. – Jonathan Gilbert Jan 27 '17 at 21:46
  • Yes, but you lose the ability to easily send composite values([Flags]) if you use strings. The ordering issue can be fixed by setting the integers manually. – Hristo Kolev Jan 27 '17 at 22:03
1

In .Net Core you can add below statement in ConfigureServices() method in StartUp.cs or Program.cs bepending on what version of .NET Core you are using.

services.AddControllers()
    .AddJsonOptions(opt=> { opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); });
AbidCharlotte49er
  • 914
  • 1
  • 8
  • 11