1

I have an ASP.Net Core 2.2 MVC application and I'm looking for some help with a specific case.

I have setup localization with resource files for 8 languages and this is working fine, both server and client side including the translation of Enum values with

<select asp-items="@Html.GetEnumSelectList<Status>()" ...

I am however struggling with the following case: in a certain view I don't want to show all the values of an Enum so I am iterating through the Enum values in the constructor of the model that I pass to the view and build a SelectList.

public class BranchViewModel
    {
        public MdxBranch Branch { get; set; }
        public List<SelectListItem> Status { get; set; }

        public BranchViewModel()
        {
            // Create a SelectList for Status skipping or disabling certain enum values
            Status = new List<SelectListItem>();
            foreach (var enumItem in Enum.GetValues(typeof(Status)))
            {
                if (enumItem.GetHashCode() == (int)Status.SystemHidden || enumItem.GetHashCode() == (int)Status.ToVerify)
                {
                    continue;   // skip this value
                }
                SelectListItem sli = new SelectListItem()
                {
                    Value = enumItem.GetHashCode().ToString(),
                    // get the Display Name
                    Text = EnumExtension.GetDisplayAttribute((Status)enumItem)
                };
                Status.Add(sli);
            }
        }
    }

To fill the Text of a SelectListItem I'm calling an extension that should retrieve the localized DisplayAttribute (which I found as a solution in the many articles I have read about this)

The Extension method

public static class EnumExtension
    {
        public static string GetDisplayAttribute(this Enum value)
        {
            return
                value
                    .GetType()
                    .GetMember(value.ToString())
                    .FirstOrDefault()
                    ?.GetCustomAttribute<DisplayAttribute>()
                    ?.GetName()
                ?? value.ToString();
        }
    }

When in debug I can see that every enum value is found but that it always returns the default display name as in the Enum definition. Again, if I use the Enum in a view with @Html.GetEnumSelectList then it generates a translated list.

The Enum definition

public enum Status
    {
        [Display(Name = "Open")]
        Open,
        [Display(Name = "Locked")]
        Locked,
        [Display(Name = "Offline")]
        Offline,
        [Display(Name = "Archived")]
        Archived,
        [Display(Name = "To verify")]
        ToVerify,
        [Display(Name = "System, hidden")]
        SystemHidden = 9
    }

In the view I'm displaying the result with a select-tag

<select asp-for="Branch.Status" asp-items="Model.Status" class="form-control" ..."></select>

The one thing I was thinking about is the way that I pass the Enum to the extension method where I convert the current item from Enum.GetValues back into an Enum:

Text = EnumExtension.GetDisplayAttribute((Status)enumItem)

The Enum defintion is not in the main project of the solution, where the view and controller reside, and the translation is made with a shared resource file for the project (not the solution). The extension method is in the same project/namespace as the Enum definition.

Any guidance would be greatly appreciated.

EDIT 1. added the select tag used to display the Enum in the view

EDIT 2. fixed the typo with MdxStatus/Status

EDIT 3. I tried translating the DisplayAttribute of a 1 option of the Enum using a variable of the Enum type but also that didn't return a translation.

For testing I added the following code in the foreach loop:

Status statusOpen = Status.Open;
var abc = statusOpen
        .GetType()
        .GetMember(statusOpen.ToString())
        .FirstOrDefault()
        ?.GetCustomAttribute<DisplayAttribute>()
        ?.GetName();

The result in abc is the default value of the Name attribute. For debugging purposes I then broke down the Fluent syntax into its individual pieces so I could track what was happening.

var gT = statusOpen.GetType();
var gM = gT.GetMember(statusOpen.ToString());
var fD = gM.FirstOrDefault();
var gCA = fD.GetCustomAttribute<DisplayAttribute>();
var gN = gCA.GetName();

Again the result in gN is the default value of the Name attribute. I saw 2 errors in debug, 1 for gT (GetType) and 1 for gM (getMember)

GetType  
Name: DeclaringMethod  
Value: '((System.RuntimeType)gT).DeclaringMethod' threw an exception of type
   'System.InvalidOperationException'  
Type: System.Reflection.MethodBase {System.InvalidOperationException}  

GetMember  
Name: FieldHandle  
Value: '((System.Reflection.MdFieldInfo)gM[0]).FieldHandle' threw an exception of type 'System.NotSupportedException'  
Type: System.RuntimeFieldHandle {System.NotSupportedException}

I am not sure if these errors are part of the issue.

2
  • What's the question now? Is that when you use <select asp-for="Branch.Status" asp-items="Model.Status" class="form-control" ..."></select>, then the listitems are not translated? Commented Jul 8, 2020 at 10:13
  • Yes, that is correct. Based on some additional testing (or trail & error) it seems to me that the issue is with the translation on server side, not with the code on client side (see my Edit 3) Commented Jul 8, 2020 at 13:06

2 Answers 2

0

Did you try to change your model so that the type of the property Status is a List of MdxStatus?

public class BranchViewModel
{
    public MdxBranch Branch { get; set; }
    public List<MdxStatus> Status { get; set; }

    public BranchViewModel()
    {
        // Create a SelectList for Status skipping or disabling certain enum values
        Status = new List<MdxStatus>();
        foreach (var enumItem in Enum.GetValues(typeof(Status)))
        {
            if (enumItem.GetHashCode() == (int)Status.SystemHidden || enumItem.GetHashCode() == (int)Status.ToVerify)
            {
                continue;   // skip this value
            }
            
            Status.Add(enumItem);
        }
    }
}

and then

<select asp-for="Status" asp-items="@Html.GetEnumSelectList<MdxStatus>()" ...

EDIT -- I See that you are using both "Status" and "MdxStatus", are they the same?

Sign up to request clarification or add additional context in comments.

11 Comments

I tried your suggestion: I needed a cast in the line Status.Add((Status)enumItem) but I get an error in the view "Cannot implicitly convert type 'System.Collections.Generic.List<mdXoneDAL.Models.Status>' to 'System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>'. An explicit conversion exists (are you missing a cast?)" And per your question about Status and MdxStatus, yes they are the same, it's a typo on my end
I am not using the @Html.GetEnumSelectList in this case but a <select<-tag <select asp-for="Branch.Status" asp-items="Model.Status" class="form-control" … "></select> I've updated my original question
did you chenge the "Status" property type of the model to "List<Status>"?
yes I did, that's why I also needed to cast enumItem to (Status)
I'm sorry, I'm missing something. Could you please re-past the code of BranchViewModel?
|
0

I haven't found a solution yet so I reverted back to my previous work-around, might be useful for others.

I filter the result of @Html.GetEnumSelectList as following:

@{ string exclSystem = "9"; }  
…  
<select asp-for="Status" asp-items="@Html.GetEnumSelectList<Status>().Where(w => !w.Value.Equals(exclSystem))" class="form-control" ...></select>

If I need to disable certain values I execute a function with DOMContentLoaded

function mdxstatusloaded() {
        let dd = document.getElementById("Status");
        let opt = dd.options;
        for (var i = 0; i < dd.options.length; i++) {
            if (opt[i].value == 4) {
                opt[i].disabled = true;
            }
        }
    }

It is not my preferred solution as I need to put this type of business logic in 3 views instead of in a single method but I need it to work, the correct language is very important.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.