2

Looking for help with Newtonsoft Json on asp.net core 2.2. I have a JsonEnumConverter<T> which was responsible for serializing/deserializing values from DescriptionAttribute from an Enum type. It was working fine until about 2 weeks ago and now it has completely stopped working.

here's what I have:

//From PerformersController: 
public async Task<ActionResult<PagedPerformers>> GetPagedPerformersAsync([FromQuery] PerformerRequest performerRequest) { ... }

    [JsonObject]
    public class PerformerRequest : PageRequest
    {
        [FromQuery(Name = "performer_id")]
        [JsonProperty(PropertyName = "performer_id", Order = 1)]
        public override string Id { get; set; }
        ....
    }

    [JsonConverter(typeof(JsonEnumConverter<SortDirectionType>))]
    public enum SortDirectionType
    {
        [Description("asc")]
        ASCENDING,
        [Description("desc")]
        DESCENDING
    }

    public abstract class PageRequest
    {
        [FromQuery(Name = "page")]
        [JsonProperty("page")]
        public int Page { get; set; }

        [FromQuery(Name = "limit")]
        [JsonProperty("limit")]
        public int PageSize { get; set; } = 100;

        [FromQuery(Name = "sort_field")]
        [JsonProperty("sort_field")]
        public string SortField { get; set; } //= "Id";

        [FromQuery(Name = "sort_dir")] [JsonConverter(typeof(JsonEnumConverter<SortDirectionType>))]
        [JsonProperty("sort_dir")]
        public SortDirectionType SortDirection { get; set; }

        [FromQuery(Name = "id")]
        [JsonProperty("id")]
        public virtual string Id { get; set; }
    }

    public class JsonEnumConverter<T> : JsonConverter where T : struct, IComparable, IConvertible, IFormattable
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(T);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var type = typeof(T);

            if (!type.IsEnum) throw new InvalidOperationException();

            var enumDescription = (string)reader.Value;

            return enumDescription.GetEnumValueFromDescription<T>();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var type = typeof(T);

            if (!type.IsEnum) throw new InvalidOperationException();

            if (value != null)
            {
                if (value is Enum sourceEnum)
                {
                    writer.WriteValue(sourceEnum.GetDescriptionFromEnumValue());
                }
            }
        }
    }

    public static class EnumExtensions
    {
        public static string GetDescriptionFromEnumValue(this Enum @enum)
        {
            FieldInfo fi = @enum.GetType().GetField(@enum.ToString());

            DescriptionAttribute[] attributes =
                (DescriptionAttribute[])fi.GetCustomAttributes(
                typeof(DescriptionAttribute),
                false);

            if (attributes != null &&
                attributes.Length > 0)
                return attributes[0].Description;
            else
                return @enum.ToString();
        }

        public static T GetEnumValueFromDescription<T>(this string description)
        {
            var type = typeof(T);

            if (!type.IsEnum)
                throw new InvalidOperationException();

            foreach (var field in type.GetFields())
            {
                if (Attribute.GetCustomAttribute(field,
                    typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
                {
                    if (attribute.Description == description)
                        return (T)field.GetValue(null);
                }
                else
                {
                    if (field.Name == description)
                        return (T)field.GetValue(null);
                }
            }

            throw new ArgumentException($"No matching value for enum {nameof(T)} found from {description}.",$"{nameof(description)}"); // or return default(T);
        }
}

this was working absolutely fine until recently. Now I'm not sure whats going on I get ValidationProblemDetails response right away. If I suppress asp.net core 2.2 model state invalid filter then modelState.IsValid will still have false. If I put a breakpoint in ReadJson of my JsonEnumConverter it wont even hit. Even tried to set JsonSerializerSettings in startup with no success or luck. Have already tried replacing Description with EnumMember and StringEnumConverter. Still the same issue. Seems like there is some issue with ModelBinder and Json.NET not playing well with each other.

NOTE: This issue is happening on ASP.NET Core 2.2. Suggesting solutions for 3.0 is not helpful!!

Selim Yildiz
  • 5,254
  • 6
  • 18
  • 28
BRBdot
  • 758
  • 2
  • 8
  • 20
  • 1
    Did you move to [tag:asp.net-core-3.0]? If so, Json.NET is no longer the default serializer, instead Microsoft introduced their own homebrew serializer [tag:system.text.json]. See: [Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0?](https://stackoverflow.com/a/55666898/3744182) which explains how to revert back to Json.NET. – dbc Dec 19 '19 at 04:49
  • I did not. still using ```netcoreapp2.2```. – BRBdot Dec 19 '19 at 06:11
  • If you are using aspnet core 3, you can try this library : https://github.com/StefH/System.Text.Json.EnumExtensions – Stef Heyenrath Jan 06 '20 at 18:56
  • @StefHeyenrath using asp.net core 2.2...While this is a great solution it doesn't really help my cause. Thanks for your help! – BRBdot Jan 06 '20 at 19:56
  • @BRBdot did you ever find a solution for .net core 2.2? I've hit the same problem... – Ben Power Oct 02 '20 at 03:29
  • If you're using .net core 2.2+ then you should definitely checkout @StefHeyenrath's response above re: github link. It worked 100% for me!! – BRBdot Oct 02 '20 at 14:41
  • 1
    @StefHeyenrath if you would like to upgrade your comment to answer I'll mark it as solved. – BRBdot Oct 02 '20 at 15:34
  • @BRBdot : I did add an answer. – Stef Heyenrath Oct 05 '20 at 13:04

1 Answers1

2

If you are using aspnet core 3 / netstandard 2.1, you can try this library https://github.com/StefH/System.Text.Json.EnumExtensions which defines some extensions to the JsonStringEnumConverter to support attributes like EnumMember, Display and Description.

Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121