Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Support for EnumMember(Value = ) #73

Open
ryan-morris opened this issue Oct 20, 2023 · 2 comments
Open

Feature Request: Support for EnumMember(Value = ) #73

ryan-morris opened this issue Oct 20, 2023 · 2 comments

Comments

@ryan-morris
Copy link

Request

Would it be possible to add support for the EnumMember attribute?

[EnumExtensions]
enum UserTypeTest
{
    [Display(Name = "مرد")]
    [EnumMember(Value = "men_value")]
    Men,

    [Display(Name = "زن")]
    [EnumMember(Value = "women_value")]
    Women,

    //[Display(Name = "نامشخص")]
    None
}

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");

        var canParse = UserTypeTestExtensions.TryParse("men_value", out var parsed);

        if (canParse)
        {
            Console.WriteLine($"Parsed men_value as {parsed}");
        }
        else
        {
            Console.WriteLine("Unable to parse using EnumMember attribute");
        }

        // ideally something like this that would return string values: men_value, women_value
        var allValues = UserTypeTestExtensions.GetValues();

        // ideally something like this for fetching the actual Value that is set: men_value or women_value based on actual enum
        // var value = UserTypeTest.Men.ToValueFast();
    }
}

Use Case

For our use case, we have DTO classes that are mapped from JSON and validated

{
   "npc_gender": "men_value",
}
class RequestDto
{
   [JsonPropertyName("npc_gender")]
   public string? NpcGender {get; init;}
}

Validation happens, then it's mapped if valid:

class Request
{
   public required UserTypeTest UserType {get; init;}
}

We use currently have manual mapping to internal types, but ideally custom enum handling in things like mapperly or other cases of using an auto mapper someone could make use of these methods going from RequestDto to Request

VERY Bad implementation

I won't pretend to even have suggestions on how this would be implemented properly or efficiently, but this is a rough version of what I use now:

internal class Program
{

    static void Main(string[] args)
    {
        var emValue = UserTypeTest.Women.ToValueStringFast();
        var canParse = EnumExtensions.TryParse<UserTypeTest>(emValue, out var back);

        Console.WriteLine($"CAN PARSE: {canParse} PARSED: {emValue}; BACK: {back}");

        var canParse2 = EnumExtensions.TryParse<UserTypeTest>("None", out var back2);

        Console.WriteLine($"CAN PARSE: {canParse2} PARSED: BACK2: {back2}");

    }
}

// bad implementations
static class EnumExtensions
{
    public static string ToValueStringFast(this Enum enumVal)
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(EnumMemberAttribute), inherit: false);

        if (attributes.Length == 0)
        {
            throw new Exception($"EnumMember attribute not found for {enumVal?.ToString()} of {enumVal?.GetType()}");
        }

        if (((EnumMemberAttribute)attributes[0]).Value == null)
        {
            throw new Exception($"EnumMember value for {enumVal?.ToString()} of {enumVal?.GetType()} was null. Cannot get as string.");
        }

        return ((EnumMemberAttribute)attributes[0]).Value!;
    }

    public static bool TryParse<T>(string? value, out T? val)
        where T : Enum
    {
        var type = typeof(T);

        foreach (var field in type.GetFields())
        {
            EnumMemberAttribute? attribute = Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) as EnumMemberAttribute;
            if (attribute != null)
            {
                if (attribute.Value == value)
                {
                    var emValue = field.GetValue(null);
                    if (emValue == null)
                    {
                        throw new Exception($"Enum member for {value} as {typeof(T)} was null");
                    }

                    val = (T)emValue;
                    return true;
                }
            }
        }

        val = default;
        return false;
    }
}
@andrewlock
Copy link
Owner

So this behaviour is sort of already supported, but it uses the [Display] or [Description] attributes instead. Unfortunately, I think that's actually going to be a problem for your example, where you don't want [Display] to be the serialized value (presumably)🤔 We could add support for [EnumMember] in the same way, but I don't think that will help you in the end given you want both attributes. I'm not sure how to support both without making breaking changes unfortunately...

@ryan-morris
Copy link
Author

ryan-morris commented Oct 20, 2023

You're right, I would like to avoid using Display for that so other scenarios with html where both may be in use:

<option value="EnumMemberValue">DisplayValue</option>

I can rip out the relevant pieces from here around Display and just add local versions of more specific methods for my use case. I just figured it wouldn't hurt to ask first.

Thanks for the quick response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants