Enum response is numeric value and not string - c#

For all my other enum classes swagger shows the string definition but for one enum class that I use in my 'ExceptionMiddleware' class it shows the numeric value. But in the swagger documentation example it shows the string value..
My enum class :
public enum ErrorCode
{
Undefined = -1,
None = 0,
ContractNotFound = 1000
}
One of my other enum classes that doesn't have this "problem" :
public enum ContractStatus
{
Undefined = 0,
Created = 1,
Valid = 2,
Invalid = 3
}
A result when the contract is not found :
I also have to add '[JsonPropertyName("errorCode")]' so the properties start with a lowercase letter. For all my other models this is not needed...
The class :
public class ExceptionResponse
{
[JsonPropertyName("errorCode")]
public ErrorCode ErrorCode { get; set; }
[JsonPropertyName("errorCodeLabel")]
public string ErrorCodeLabel { get; set; }
[JsonPropertyName("errorMessage")]
public string ErrorMessage { get; set; }
}
Configuration in 'Program.cs' :
o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
If I remove this all enum's show numeric values instead of string values.
How I build the 'ExceptionResponse' model in my 'ExceptionMiddleware' class :
var exceptionResponse = new ExceptionResponse()
{
ErrorCode = ErrorCode.Undefined,
ErrorCodeLabel = ErrorCode.Undefined.ToString(),
ErrorMessage = "A random message."
};
And if there is an error :
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(exceptionResponse));

Because you are manually serializing, you are bypassing the registered middleware that contains your converter. You need to pass an options instance to your serialize call that includes the converter.
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(exceptionResponse, options));
It is considered a best practice to cache your serializer options as a static field so that you are not re-creating them each call to Serialize, since this has severe performance repercussions in older versions of System.Text.Json.
public class ContainingClass
{
private static readonly JsonSerializerOptions s_options = new()
{
Converters =
{
new JsonStringEnumConverter()
}
}
}

You can put this attribute above your Enum declaration. Something like this
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ErrorCode
{
Undefined = -1,
None = 0,
ContractNotFound = 1000
}
Note that this is for .net core 3 and above. More info for the built-in converter is here: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonstringenumconverter?view=net-7.0

Related

how to display enum number instead of enum name in response

When trying to get an answer using hotchocolate, in fields with enum type the answer is in the form of a string, and I need an enum value
[UseProjection]
public IQueryable<Cart> GetActualCart([Service] ApplicationDbContext context, ClaimsPrincipal claimsPrincipal, int pageNumber = 1, int pageSize = 10)
{
var userId = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier);
return context.Carts
.Where(f => f.CourierId == new Guid(userId))
.FromPage(pageNumber, pageSize);
}
public class Cart : AuditableBaseEntity<Guid>
{
public Guid CourierId { get; set; }
public CartStatus Status { get; set; } = CartStatus.Building;
public virtual ICollection<CartItem> Items { get; set; } = new HashSet<CartItem>();
}
public enum CartStatus
{
Building = 1,
WarehouseDelivery = 2,
WarehouseRefund = 3,
CartRefund = 4,
}
RESPONSE:
"data": {
"actualCart": [
{
"courierId": "efb60c9e-c6fe-4479-bd93-82fb23ad63b5",
"status": "BUILDING"
},
{
"courierId": "efb60c9e-c6fe-4479-bd93-82fb23ad63b5",
"status": "WAREHOUSE_DELIVERY"
}
]
A GraphQL enum will always yield the name as a response. Infact the GraphQL enum element name cannot start with a number.
https://spec.graphql.org/October2021/#sec-Enum-Value
The name specification is here:
https://spec.graphql.org/October2021/#Name
If it is acceptable that the Cart.status field is of type Int instead of being an enum type, you can bind the CartStatus to the IntType and use a converter:
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.BindRuntimeType<CartStatus, IntType>()
.AddTypeConverter<CartStatus, int>(source => (int) source);
}
First of all, consumers of your API should not care for the numeric enum value. If they do it's a clear sign that your abstraction is leaking and your consumers are depending on an implementation detail of your API.
That being said, as Michael already mentioned there is no spec compliant way to return a number literal as an enum value. You could work around this, but then your server would no longer be spec compliant, which will introduce a whole new suit of issues for you.
The simplest way to solve your problem would be to just swap your enum for the Int scalar:
builder.Services
.AddGraphQLServer()
// Each occurence of the CartStatus should be represented as Int
.BindRuntimeType<CartStatus, IntType>()
// How to convert a CartStatus to the integer value (output)
.AddTypeConverter<CartStatus, int>(source => (int)source)
// How to convert an int to the CartStatus enum value (input)
.AddTypeConverter<int, CartStatus>(source => (CartStatus)source);
If you REALLY need the number value, but you also want to keep the enum for discoverability's sake, you could create a wrapper type that returns both the enum value and the number value. Assuming your enum is named Status you can create a wrapper object like the following:
builder.Services
.AddGraphQLServer()
.BindRuntimeType<Status, StatusObjectType>();
public class StatusObjectType : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("StatusObject");
descriptor.Field("value")
.Type<EnumType<Status>>()
.Resolve(context => context.Parent<Status>());
descriptor.Field("code")
.Resolve(context => (int)context.Parent<Status>());
}
}
For every resolver that returns the Status enum it will automatically map to this wrapper object.
A consumer would then be able to query your enum field like this:

.Net Core Enum Values not serializing after getting the response

In .Net Web API core solution i have a class(Message) with variables of enum type as mentioned below
public enum MessageCode
{
[EnumMember]
NULL_PARAMETER,
BLANK_PARAMETER,
EMPTY_PARAMETER,
INVALID_PARAMETER,
PARAMETER_TRUNCATED,
QUERY_NOT_FOUND,
TERM_NOT_FOUND,
LIST_NOT_FOUND,
NO_SEARCH_RESULTS,
NO_UPDATES,
NO_DICTIONARY,
NO_PERMISSION,
LOCKED_PROTOCOL,
NO_TERMS_IN_LIST,
DUPLICATE_TERM
}
public enum MessageType
{
INFO,
WARNING,
ERROR,
FATAL
}
public class Message
{
[JsonConverter(typeof(StringEnumConverter))]
public MessageType MessageType { get; set; }
public bool MessageTypeSpecified;
[JsonConverter(typeof(StringEnumConverter))]
public MessageCode MessageCode { get; set; }
public bool MessageCodeSpecified;
public string MessageParameters;
public string MessageText;
}
While getting the response for the object (Message) using postman the response was as below
"messages": [
{
"messageTypeSpecified": false,
"messageCodeSpecified": false,
"messageParameters": null,
"messageText": "0"
}
]
I was not able to get the enum values in response.
so tried the below options
Decorate the Class Property - https://exceptionnotfound.net/serializing-enumerations-in-asp-net-web-api/
Decorate the Enumeration - https://exceptionnotfound.net/serializing-enumerations-in-asp-net-web-api/
Add the Converter Globally - https://exceptionnotfound.net/serializing-enumerations-in-asp-net-web-api/
Mentioning enum member ([EnumMember]) in each enum values.
Nothing worked out.
You accidentally hit a Newtonsoft feature (not very well documented). A longer description can be found in this
question.
In short: you have a property named MyPropertyName and one named MyPropertyNameSpecified,i.e Specified appended to other property name, the default behaviour for Newtonsoft is to not serialize MyPropertyName when MyPropertyNameSpecified is false.
The solution to your problem would be either to rename some of the properties or use these settings:
new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver { IgnoreIsSpecifiedMembers = true }
};
To edit JSON serializer settings in a .NET Core project supply the options in your ConfigureServies method:
services.AddMvc()
.AddJsonOptions(
options =>
{
options.SerializerSettings.ContractResolver =
new DefaultContractResolver { IgnoreIsSpecifiedMembers = true };
});

How to mark certain property as required in json content

I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.

JsonIgnore attribute conditional analog but not ShouldSerialize

I want to implement a conditional version of [JsonIgnore] attribute in C#. I don't want to use ShouldSerializePropertyName due to it's dependency on hardcoded property name.
My API model inherits from the database model and I certainly want to ignore database Ids but I also want to ignore some other properties based on which features the customer payed for. I don't want to use ShouldSerialize technique because the other developers in my team could change the database property name and accidentally make visible what should not be visible.
I read if I can optionally turn off the JsonIgnore attribute at runtime
but it looks like the suggested technique turns off all JsonIgnore together. What I want to do is to have just some of them off based on some condition.
Is there a work around? Is there some attribute which could do that? If I need to write a custom attribute, could you show me how please? Thanks!
This is an interesting problem. My answer borrows heavily from the link you provided, but checks for a custom attribute defining your "Premium Content" (things that the user paid for):
Like your link, I have defined a class Foo, which will be serialized. It contains a child object PremiumStuff, which contains things that should only be serialized if the user paid for them. I have marked this child object with a custom attribute PremiumContent, which is also defined in this code snippet. I then used a custom class that inherits from DefaultContractResolver just like the link did, but in this implementation, I am checking each property for our custom attribute, and running the code in the if block only if the property is marked as PremiumContent. This conditional code checks a static bool called AllowPremiumContent to see whether we are allowing the premium content to be serialized. If it is not allowed, then we are setting the Ignore flag to true:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public string AlternateName { get; set; }
[PremiumContent]
public PremiumStuff ExtraContent { get; set; }
}
class PremiumStuff
{
public string ExtraInfo { get; set; }
public string SecretInfo { get; set; }
}
class IncludePremiumContentAttributesResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (var prop in props)
{
if (Attribute.IsDefined(type.GetProperty(prop.PropertyName), typeof(PremiumContent)))
{
//if the attribute is marked with [PremiumContent]
if (PremiumContentRights.AllowPremiumContent == false)
{
prop.Ignored = true; // Ignore this if PremiumContentRights.AllowPremiumContent is set to false
}
}
}
return props;
}
}
[System.AttributeUsage(System.AttributeTargets.All)]
public class PremiumContent : Attribute
{
}
public static class PremiumContentRights
{
public static bool AllowPremiumContent = true;
}
Now, let's implement this and see what we get. Here is my test code:
PremiumContentRights.AllowPremiumContent = true;
Foo foo = new Foo()
{
Id = 1,
Name = "Hello",
AlternateName = "World",
ExtraContent = new PremiumStuff()
{
ExtraInfo = "For premium",
SecretInfo = "users only."
}
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IncludePremiumContentAttributesResolver();
settings.Formatting = Formatting.Indented;
string json = JsonConvert.SerializeObject(foo, settings);
Debug.WriteLine(json);
In the first line, PremiumContentRights.AllowPremiumContent is set to true, and here is the output:
{
"Id": 1,
"Name": "Hello",
"ExtraContent": {
"ExtraInfo": "For premium",
"SecretInfo": "users only."
}
}
If we set AllowPremiumContent to False and run the code again, here is the output:
{
"Id": 1,
"Name": "Hello"
}
As you can see, the ExtraContent property is included or ignored depending on the state of the AllowPremiumContent variable.

Weird names in WCF metadata when using Generic classes

I am facing a very weird problem while creating a wcf data service with NHibernate 3.0
This is my concrete class which I want to expose as part of data service
public class PayeeType : LookupIdentifierType<int, CamConnect.Entities.DomainEntities.Lookup.PayeeType, PayeeTypeIdentifiers>
{
public virtual String TypeName { get; set; }
}
This is the base class (generic) definition
[IgnoreProperties("Identifier")]
public abstract class LookupIdentifierType<TId, TDto, TIdentifier>
{
public virtual TIdentifier Identifier
{
get
{
return (TIdentifier)Enum.Parse(typeof(TIdentifier), Id.ToString());
}
//set
//{
// String idString = (Convert.ChangeType(value, value.GetTypeCode())).ToString();
// Id = Int32.Parse(idString) as TId;
//}
set
{
//String idString = (Convert.ChangeType(value, value.GetTypeCode())).ToString();
Id = (TId)Convert.ChangeType(value, typeof(TId));
}
}
public virtual String IdentifierText
{
get { return Enum.GetName(typeof(TIdentifier), Identifier); }
set
{
System.Globalization.TextInfo myTI = new System.Globalization.CultureInfo("en-US", false).TextInfo;
TIdentifier temp;
bool success = Enum.TryParse<TIdentifier>(value, true, out temp);
if (!success) Enum.TryParse<TIdentifier>(myTI.ToTitleCase(value).Replace(" ",""), true, out temp);
Identifier = temp;
}
}
public static TReturn GetForIdentifier<TReturn>(TIdentifier identifier) where TReturn : class, ILookupIdentifierType<TIdentifier>
{
Type constructedType = typeof(TReturn);
ILookupIdentifierType<TIdentifier> returnObj = Activator.CreateInstance(constructedType) as ILookupIdentifierType<TIdentifier>;
returnObj.Identifier = identifier;
return returnObj as TReturn;
}
}
Now when I see the service metadata I get very weird names for my base types
<Schema Namespace="Entities.Entities.Lookup"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityType Name="PayeeType"
BaseType="Entities.Entities.Lookup.LookupIdentifierType
_x0060_3_x005B_System.Int32_x0020_.Entities.DomainEntities.Lookup.
PayeeType_x0020.Entities.Identifiers.PayeeTypeIdentifiers_x005D_" />
This causes the add reference functionality to fail cause (as far as I know) WCF data service doesnt allow underscores in the name and I get 'Name' attribute is invalid error.
Has anybody encountered this before? How can I change the name of the generic base types?
I think you need to specify the data contract name for the base class explicitly when using generics.
See
Naming Generic DataContracts in WCF

Categories

Resources