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?
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.
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.
}
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.
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.
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()); });
Related
I often encounter this situation when developing web apps where the client can choose/set an enum value (maybe through an API, not providing a select box like in frontend).
So I need to handle incorrect values. I am using FluentValidation with Swagger for this. I want to display enums as strings (Swagger) so I register the converter:
services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
I have weird findings for enum modelbinding in .NET CORE:
Consider enum { A,B,C } and tested value bindings:
value "a" -> Enum.A -> as expected
value 1 -> Enum.B -> as expected
value 10 -> 10 -> o_0 - is that new behaviour in CORE?
value "x" -> binding error -> as expected
value null -> Enum.A -> as expected
But how would I deal best with an invalid values?
I certainly do not want value 10 or Enum.A for null values.
I could think of two approaches:
i) Handle all invalid values like binding errors
ii) Bind invalid values to a an explicit invalid enum default Enum.Undefined and validate that this value never occurs
i) How would I enforce stricter binding?
ii) How can I force binding invalid values to Enum.Undefined?
UPDATE
I realized that 3) is unchanged behaviour, I just did not came across this issue. I find that very counter intuitive though.
I also realized that I could write my own enum model binder but I do not want to overengineer things.
What I did for now is following:
enum MyEnum { undefined, a,b ,c}
and in FluentValidation validator:
this.RuleFor(x => x.MyEnum ).Must(
x =>
{
if (!Enum.IsDefined(x)) // can be crap!
{
return false;
}
// still can be Undefined
return x != MyEnum.Undefined;
});
This way I handle incorrect int values. Incorrect string values can not be handled that way because they will break at .NET model binding level. I guess I can live with that.
UPDATE 2
After some more thoughts about that issue I came up with following solution:
I just use string over enum in my DTO and therefore I can completely workaround model binding issues and use validations as follows:
this.RuleFor(x => x.Type).Must(
x => Enum.TryParse(typeof(MyEnum), x, out _));
My Web API where one of GET endpoint return collection of ProductsByCategory.
public async Task<ActionResult<IEnumerable<ProductsByCategory>>> GetProductsByCategories()
{
var productsByCategories = await northwindContext.Categories.Select(category => new ProductsByCategory
{
CategoryName = category.CategoryName,
ProductCount = category.Products.Count
}
).ToListAsync();
return productsByCategories;
}
But I know that JSON and even XML don`t need in name of the type. They just need in name of the property and its value. So I have another way how to implement this method(using anonymous types):
public async Task<ActionResult<IEnumerable>> GetProductsByCategories()
{
var productsByCategories = await northwindContext.Categories.Select(category => new
{
CategoryName = category.CategoryName,
ProductCount = category.Products.Count
}
).ToListAsync();
return productsByCategories;
}
I liked second approach because I don't need in writing code of such types like ProductsByCategory and seems to me more logical since JSON(XML) don't need in type's name I don't need to create not anonymous type. But this is all my thoughts and I am pretty new in field of ASP .NET Core and it is possible that I miss some parts and my guesses are wrong. So is it good practise to pass collection of anonymous types as respond to HTTP GET request or is it better to specify what type of items is collection keeps?
You right, you will have the same resulting serialized object (xml, json), and you can use anonymous type. But you should keep in mind:
When your explicitly define resulting class, your code will be
cleaner
For explicitly defined resulting class your may define some validation
rules using attributes for serializer
If you use documentation tools, for example swagger, you also may use attributes to provide additional documentation.
Api should not be ambiguous.
Everything depends on your preferences. If you want use anonymous type, you may.
We're developing a web application using ASP.Net MVC 5 C#, I'm having difficulties coming up with a nice solution for my problem.
We're accessing an api that has a few enums coming across as text (i.e. "yes", "no", "notfound", "na") on the model itself I'd like to have strongly typed enums that store in the database as an integer (save a little space I think) the issue comes when we're deserializing the response from the api. In the response it will comes across as text (see above) where as the enums will be expecting an integer.
How is this done? I really hate asking questions where I haven't tried anything but my web searches hasn't resulted in anything. If you needed any code please let me know and I'll update the question with the segments needed. As of right now the model has several strongly typed enums and the api is returning strings of the enum values. by the way The enum texts are the same as the return values.
In-case it makes any difference we're also using EF Code First 6
You can use Enum.TryParse to convert the string to its enum value
public enum MyEnum
{
NA
Yes,
No
}
then
MyEnum value = MyEnum.NA;
Enum.TryParse<MyEnum>(theTextValue, out value );
I am experiencing a strange behavior with very basic web service development. This question might be dumb but I think someone would be able to explain this observation.
I am developing a web service with a web method, MyWebMethod
MyWebMethod(MyEnum Param, .....)
Where,
public enum MyEnum : int
{
Type_1 =1;
Type_2 =2;
Type_3 =3;
}
Now I am using my client to communicate with this service but for every request type, Type_1, Type_2 etc the service captures it as Type_1. As an example, if I create a break point at MyWebMethod in my web service, I see Type_1 as param1 type. I guess this is a problem with Namespacing. I cannot see any other defects on the code. Any Idea based on the experiences?
When enum is serialized, only its string representation is transferred through wire (names), not the values. I believe thats the reason you are getting the wrong values.
Check out this 2 articles for more info
WebServices_and_Enums
Using enum in web service parameter
If this is a WCF web service and a .NET 2.0 client generated with wsdl.exe for each value type in the method signature there will be a boolean parameter added called XXXSpecified which you need to set to true. Check this blog post for more details.
I guess your enum does not need to inherit from int. You are providing name and value in the enumeration, that should suffice. I am assuming all your code is .NET 2.0. As test , return an enumeration value from the webservice. Just to make sure XML Serialization is working as expected when the service is hit directly by the browser.
I have a web service that I am passing an enum
public enum QueryType {
Inquiry = 1
Maintainence = 2
}
When I pass an object that has a Parameter of QueryType on it, I get the error back from the web service saying:
'2' is not a valid value for QueryType
when you can clearly see from the declaration of the enum that it is.
I cannot change the values of the enum because legacy applications use the values, but I would rather not have to insert a "default" value just to push the index of the enum to make it work with my web service. It acts like the web service is using the index of the values rather than the values themselves.
Does anybody have a suggestion of what I can do to make it work, is there something I can change in my WSDL?
I'm assuming you are using asmx web services for this answer.
Your guess is right -- the XML Serializer uses the enumeration names in the WSDL and not the value.
If you look at your WSDL it will look something like this:
<s:simpleType name="QueryType">
<s:restriction base="s:string">
<s:enumeration value="Inquiry" />
<s:enumeration value="Maintainence" />
</s:restriction>
</s:simpleType>
So, when you invoke the service it is expecting a string that is the name of the enumeration member. When you use a .NET proxy, this conversion is usually handled for you. If a value is passed to the service that cannot be deserialized into the enum value you will get the message that you are seeing.
To get around this, you can ensure you are sending it the expected value or, if that doesn't work for you, you can tell the XML Serializer what values you want to use. You can do this using the XmlEnum attribute:
public enum QueryType
{
[XmlEnum("1")]
Inquiry = 1,
[XmlEnum("2")]
Maintainence = 2
}
This will generate the following schema fragment (from the WSDL):
<s:simpleType name="QueryType">
<s:restriction base="s:string">
<s:enumeration value="1" />
<s:enumeration value="2" />
</s:restriction>
</s:simpleType>
Then if you are passing the value "2" into the service then it should be deserialized properly but you lose the meaning of the enumeration values.
Try adding the Flags() attribute to the QueryType definition.
Something to consider is to make sure that you initialize your enum values and not assume the first item from your list will be used.
For example, having an enum defined that does not start at the int value of 0 such as this:
public enum EnumCategoryID
{
TOOTH_PASTE = 1,
TOOTH_BRUSHES = 2,
HOT_BEVERAGES = 3,
ENERGY_DRINKS = 4,
OVER_THE_COUNTER = 5,
IN_STORE = 6
}
Normally when you declare your enum instance you may be tempted to do this:
EnumCategoryID anID; //Assuming the value will be 'TOOTH_PASTE' when used
If the data structure that you return through the webservice has a enum variable attached to it you WILL receive the instance error if called from outside the webservice (but if you use the dataclass from within the webservice you will have no errors).
Point is, just be sure that you have properly initialized your enum instance that you are returning in the webservice.