I'm using ServiceStack v 3.9.71 and the ServiceStack.Text.EnumMemberSerializer assembly to serialize enums into readable text.
This works great, my enum values are serialized into the name I've specified using the EnumMemberAttribute.
The problem, though, is Swagger does not use my names. My guess is it just calls the .ToString() method on the enum values rather than the EnumMemberAttribute value.
Here is the order in which I setup the serialization. (In AppHost):
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Plugins.Add(new SwaggerFeature());
It doesn't seem to matter if the enum serializer is set before or after the swagger feature is added.
You are correct that the Swagger code does not use ServiceStack.Text.EnumMemberSerializer when parsing enum values. It only uses an Enum.GetValues here. Note that this is still the same in v4.
You can submit a pull request to make this change, but I'm not familiar with EnumMemberSerialzer and how it allows for retrieving the list of enum options. You may instead be able to use a string property decorated with ApiAllowableValues to achieve the affect.
Here is the solution I came up with (with the help of bpruitt-goddard, thanks mate):
The enum:
public enum MyEnum
{
[EnumMember(Value = "Value One")]
Value1 = 1,
[EnumMember(Value = "Value Two")]
Value2 = 2,
[EnumMember(Value = "Value Three")]
Value3 = 3
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues("MyEnum", "Value One", "Value Two", "Value Three")]
public MyEnum MyEnum { get; set; }
}
Inside the AppHost:
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Now the enum is serialized properly and the Swagger documentation is correct. The only issue with this is having the names in two different places. Perhaps there is a way to check the names match via a unit test.
I came up with, in my opinion, a better solution. I wrote a class that extends the ApiAllowableValuesAttribute:
public class ApiAllowableValues2Attribute : ApiAllowableValuesAttribute
{
public ApiAllowableValues2Attribute(string name, Type enumType)
: base(name)
{
List<string> values = new List<string>();
var enumTypeValues = Enum.GetValues(enumType);
// loop through each enum value
foreach (var etValue in enumTypeValues)
{
// get the member in order to get the enumMemberAttribute
var member = enumType.GetMember(
Enum.GetName(enumType, etValue)).First();
// get the enumMember attribute
var enumMemberAttr = member.GetCustomAttributes(
typeof(System.Runtime.Serialization.EnumMemberAttribute), true).First();
// get the enumMember attribute value
var enumMemberValue = ((System.Runtime.Serialization.EnumMemberAttribute)enumMemberAttr).Value;
values.Add(enumMemberValue);
}
Values = values.ToArray();
}
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues2("MyEnum", typeof(MyEnum))]
public MyEnum MyEnum { get; set; }
}
Now you don't have to specify the names again or worry about a name change breaking your Swagger documentation.
Related
I have to send string with xml containing enums serialized to xml.
Those enums are in nuget package, so I can't edit them.
All enums have name, value and description attribute.
public enum FileExtenstion
[Description("XML extension")]
xml = 1,
[Description("TXT extension")]
txt = 2
end enum
My function to get xml from this enum looks like this:
public static string GetFileExtension()
{
var _cache = System.Enum.GetValues(typeof(FileExtension))
.Cast<FileExtension>()
.Select(enumValue => new EnumToXmlSerializer
{
Name = enumValue.ToString(),
Value = (int)enumValue,
Description = enumValue.GetDescription()
}).ToList();
(...)
}
public class EnumToXmlSerializer
{
public string Name { get; set; }
public int Value { get; set; }
public string Description { get; set; }
}
Works well.
But I have a lot of enums to convert and better way will be to prepare some parametrized function with convert string with name of enum ("FileExtension") to type.
I haven't idea how to convert enum name (not enum every value name but enum type name) and convert its to type. In stackoverflow I found only conversion for enum values not all types.
I'm looking for solution to prepare function like this:
public string GetXmlFromEnumTypeName("FileExtension")
to get:
<enums>
<enum><name>xml</name><value>1</value><description>XML extension</description></enum>
<enum><name>txt</name><value>2</value><description>TXT extension</description></enum>
</enums>
I suppose there will be one line to change my GetFileExtension function but I usually get it wrong about things like that.
This is different from questions like below
How to tell if an enum property has been set? C#
I am working on creating WCF Rest service using existing DataContract classes. I cannot change property datatypes like enum to enum? and also cannot add new option in my enum like undefined, none, or by default set anything since if I do anyone of these changes it will be firm wide impact and many applications depend on it.
Normally people call my WCF REST Service using applications like POSTMAN where they send in json data like below in which Gender is an enum with Male, Female, Transgender, etc. If they do not send it my service throws exception and I want to add validation logic to check whether enum is null or not when QA scall my service using POSTMAN and send JSON data even though it is not nullable and also do not have any None, Null options in my enum? If it is NULL I want to send ArgumentNullException back to callers with nice message. I want to handle this situation gracefully.
public enum Gender
{
Male = 0,
Female = 1,
Transgender = 2
}
Below is good
{
"Name" : "XXX"
"Gender" : "1"
}
Below throws error
{
"Name" : "XXX"
"Gender" : ""
}
SOLUTION:
Thanks to p.s.w.g for pointing in correct direction and I marked his answer below. I am using Newtonsoft so I did like below
string stringfydata = Newtonsoft.Json.JsonConvert.SerializeObject(requestGender);
if(string.IsNullOrEmpty(stringfydata))
{
throw new ArgumentNullException("Gender value cannot be NULL or Empty.");
}
Other than the obvious option of warping the enum in a class, which might not work in your specific situation, you can set the enum variable to a integer out of the enum range. After that, you can then check to see if the integer is defined within the enum.
Since C# does not check enumerations, you can do the following:
public enum Gender
{
Male = 0,
Female = 1,
Transgender = 2
}
public int HandleGender(string strJsonGender){
if (strJsonGender == "")
{
return -1;
}
else {
// Get int representation of the gender
return (int)((Gender)Enum
.Parse(typeof(Gender),
strJsonGender, true));
}
}
public void MainMethod(string strJsonGender) {
Gender gVal;
int iVal = HandleGender(strJsonGender);
if (Enum.IsDefined(typeof(Gender), iVal))
{
// Handle if the an actual gender was specified
gVal = (Gender)iVal;
}
else {
// Handle "null" logic
}
Note: the answers below use DataContracts since you've indicated in your question, but similar solutions exist for Json.Net serialization.
You can use [DataMember(EmitDefaultValue = false)] to ignore cases where Gender is not specified at all. In this case, the value that's returned will be whatever enum member is assigned a value of 0 (note that if no member has that value, you'll still get a value of 0, which could be useful for you).
[DataContract]
class Person
{
[DataMember]
public string Name { get; set; }
[DataMember(EmitDefaultValue = false)]
public Gender Gender { get; set; }
}
void Main()
{
var json = "{\"Name\": \"XXX\"}";
var ser = new DataContractJsonSerializer(typeof(Person));
var obj = ser.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(json)));
obj.Dump(); // Person { Name = "XXX", Gender = Male }
}
To handle cases where an empty string is provided instead of a valid value or no value at all, you can use this hacky little trick:
[DataContract]
class Person
{
[DataMember]
public string Name { get; set; }
[IgnoreDataMember]
public Gender Gender
{
get
{
if (GenderValue.GetType() == typeof(string))
{
Enum.TryParse((string)GenderValue, out Gender result);
return result;
}
return (Gender)Convert.ToInt32(GenderValue);
}
set
{
GenderValue = value;
}
}
[DataMember(Name = "Gender", EmitDefaultValue = false)]
private object GenderValue { get; set; }
}
void Main()
{
var json = "{\"Name\": \"XXX\", \"Gender\": \"\"}";
var ser = new DataContractJsonSerializer(typeof(Person));
var obj = ser.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(json)));
obj.Dump(); // Person { Name = "XXX", Gender = Male }
}
However, this is somewhat awkward and can be easily abused. I'd recommend caution with this approach. As others have mentioned, we typically want to throw errors whenever invalid values are provided to a function / API. By 'failing fast' you let the user attempting to use the API know that they've constructed a request that's likely to produce unexpected results at some point.
This is my code:
[DataContract] // (Name = "Type")]
public enum Purpose
{
[EnumMember(Value = "definitionTarget")]
DefinitionTarget = 0,
[EnumMember(Value = "definitionSource")]
DefinitionSource = 1,
[EnumMember(Value = "semanticRole")]
SemanticRole = 2,
[EnumMember(Value = "dataType")]
DataType = 3
}
I want the enum values to display according to the strings given, not the integer values. For some reason, the values are ignored.
The serialization code is nothing fancy:
protected string GetRuntimeValue(RuntimeValue value)
{
MemoryStream ms = new MemoryStream();
_serializer.WriteObject(ms, value);
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
}
I went to the Microsoft documentation and found an example with some boilerplate code where they inherit IExtensibleDataObject (no explanation why). I added the code to my base class, no change.
What am I doing wrong? Should be something simple, no?
The bad news is that DataContractJsonSerializer ignores the EnumMember attribute and this is by design:
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. ... The EnumMemberAttribute and the NonSerializedAttribute attributes are ignored if used.
A Microsoft support agent also confirmed this and suggested a kludge which is to expose a property decorated with DataMember for the serializer to use instead, which calls Enum.GetName() to get the string name of the enum value.
It would need to be modified in your case to return a string with the first letter converted to lower case:
[DataContract]
public class RuntimeValue
{
public Purpose Purpose { get; set; }
[DataMember(Name = "Purpose")]
string PurposeString
{
get { return Enum.GetName(typeof(Purpose), this.Purpose).FirstCharacterToLower(); }
set { this.Purpose = (Purpose)Enum.Parse(typeof(Purpose), value, true); }
}
}
Uses an extension method borrowed from here to do the lower case stuff:
public static string FirstCharacterToLower(this string str)
{
if (String.IsNullOrEmpty(str) || Char.IsLower(str, 0))
return str;
return Char.ToLowerInvariant(str[0]) + str.Substring(1);
}
Testing:
Console.WriteLine(GetRuntimeValue(new RuntimeValue()));
Output:
{"Purpose":"definitionTarget"}
Personally I would use JSON.NET instead if possible but for the sake of my answer I am assuming that you have already considered that option and have good reason to stick with this serializer.
I've got an enum class...
public enum LeadStatus : byte
{
[Display(Name = "Created")] Created = 1,
[Display(Name = "Assigned")] Assigned = 2,
....
}
Name of course is out-of-the-box. From MetaData...
namespace System.ComponentModel.DataAnnotations
{
public sealed class DisplayAttribute : Attribute
{
...
public string Name { get; set; }
...
}
}
Suppose I wanted my own custom Display Attribution, such as "BackgroundColor"...
[Display(Name = "Created", BackgroundColor="green")] Created = 1
I've seen a few other threads here that kinda dance around the issue, but the context is different enough that I can't make it work. I assume I need to create some sort of extension / override class, but I am not picturing this in my head.
Thanks!
Having your own attribute.
public sealed class ExtrasDisplayAttribute : Attribute
{
public string Name { get; set; }
public string BackgroundColor { get; set; }
}
And this extension method.
namespace ExtensionsNamespace
{
public static class Extensions
{
public static TAttribute GetAttribute<TAttribute>(Enum value) where TAttribute : Attribute
{
return value.GetType()
.GetMember(value.ToString())[0]
.GetCustomAttribute<TAttribute>();
}
}
}
Now you can extract attribute from enum like this.
using static ExtensionsNamespace.Extensions;
//...
var info = GetAttribute<ExtrasDisplayAttribute>(LeadStatus.Created);
var name = info.Name;
var bg = info.BackgroundColor;
//...
public enum LeadStatus : byte
{
[ExtrasDisplay(Name = "Created", BackgroundColor = "Red")] Created = 1,
[ExtrasDisplay(Name = "Assigned")] Assigned = 2,
}
If you want to still use the original attribute you can have that too.
you should apply both attributes to single enum.
public enum LeadStatus : byte
{
[Display(Name = "Created"), ExtrasDisplay(BackgroundColor = "Red")]Created = 1,
[Display(Name = "Assigned")] Assigned = 2,
}
And extract each one you want.
var name = GetAttribute<DisplayAttribute>(LeadStatus.Created).Name;
var bg = GetAttribute<ExtrasDisplayAttribute>(LeadStatus.Created).BackgroundColor;
public sealed class DisplayAttribute : Attribute is a sealed class and therefore you cannot inherit it and add other behavior or properties to it.
Below is my assumption but someone can chime in if they know why
And you may wonder why .NET developers made it sealed? I am wondering the same and my assumption is because each of the properties in DisplayAttribute are used to inject javascript, html etc. If they left it open, and you added a BackgroundColor property to it, what does that mean? What would that do in the UI?
having concluded this this isn't possible, I went with another kind of solution. Not as tidy as I had hoped for originally, but it still gets the job done.
Methods inside enum in C#
I have the following sample C# code that is auto-genereated from an xsd using the svcutils.exe application.
[DataContract]
public enum Foo
{
[EnumMember(Value = "bar")]
Bar = 1,
[EnumMember(Value = "baz")]
Baz = 2
}
[DataContract]
public class UNameIt
{
[DataMember(Name = "id")]
public long Id { get; private set; }
[DataMember(Name = "name")]
public string Name { get; private set; }
[DataMember(Name = "foo")]
public Foo Foo { get; private set; }
}
The following is a unit test that attempts to deserialise a sample JSON document to the UNameIt class.
[TestClass]
public class JsonSerializer_Fixture
{
public const string JsonData = #"{ ""id"":123456,
""name"":""John Doe"",
""foo"":""Bar""}";
[TestMethod]
public void DataObjectSimpleParseTest()
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(UNameIt));
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(JsonData));
UNameIt dataObject = serializer.ReadObject(ms) as UNameIt;
Assert.IsNotNull(dataObject);
Assert.AreEqual(123456, dataObject.Id);
Assert.AreEqual(Foo.Baz, dataObject.Foo);
}
}
Unfortunately, the test fails giving the following reason:
System.Runtime.Serialization.SerializationException: There was an
error deserializing the object of type MyNamespace.Units.UNameIt. The
value 'Bar' cannot be parsed as the type 'Int64'.
The test will pass if I update my JSON string to replace the string specifier for the Enum to an integer e.g.
public const string JsonData = #"{ ""id"":123456,
""name"":""John Doe"",
""foo"":""1""}";
I do not have the flexibility to the change the supplied JSON so I have to figure out how to convert the string Enum representation perhaps on serialisation. Ideally, I would like to facilitate this without having to change my autogenerate class because once I re-generate the class I would loose my changes.
I am wondering if it would be possible to extend the DataContractJsonSerializer to custom handle Enumerations? Or perhaps there is better way to do this?
This behavior is by design. Here's a quote from the Enumerations and JSON paragraph on 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.
Moreover the DataContractJsonSerializer will automatically serialize all enumerations, so the EnumMemberAttribute is actually ignored.
For a workaround, take a look at this answer on SO.
This is work :
var ret = new JavaScriptSerializer().Deserialize<tblGridProjects>(retApi.Item2);
But you can't use datamembers attributes, so can't rename properties.
You must set the name of the property like Json sended.