ASP.NET MVCでenumを使ってRadioButtonを作るHelper

MVCやっててEnumをドロップダウンにして上手く使いたいなぁってケースは結構あるかと思います。
っていうネタを書こうと思ったらASP.NET5.1からはEnumDropDownListForが追加されていますね。
そちらを使うのが良いですねぇ。

EnumをRadioボタンにするネタ。

まずはDescriptionAttributeの拡張
画面出力名称の他にEnumの中でも選択肢として出したくないモノとかの制御をしたりします。

```csharp:PartsDescriptionAttribute.cs
    public class PartsDescriptionAttribute : System.ComponentModel.DescriptionAttribute
    {
        /// <summary>
        /// この項目が部品に表示されるかを表します。
        /// </summary>
        public bool EnableParts
        {
            get
            {
                return _enableparts;
            }
            private set
            {
                _enableparts = value;
            }
        }
        private bool _enableparts = true;
        public PartsDescriptionAttribute() { }
        public PartsDescriptionAttribute(string Descript) : base(Descript) { }
        public PartsDescriptionAttribute(string Descript, bool enableparts)
            : base(Descript)
        {
            this.EnableParts = enableparts;
        }
        public PartsDescriptionAttribute(bool enableparts)
        {
            this.EnableParts = enableparts;
        }

    }
```

次にHtmlHelper拡張

```csharp:EnumRadioButton.cs
    public static class EnumRadioButton
    {

        private static Type GetEnumTypeFromAttribute<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
        {
            MemberInfo propertyInfo =
                ((expression.Body as MemberExpression).Member as MemberInfo);
            if (propertyInfo == null)
                throw new ArgumentException("プロパティではありません。");
            EnumDataTypeAttribute attr = Attribute.GetCustomAttribute(propertyInfo,
                typeof(EnumDataTypeAttribute)) as EnumDataTypeAttribute;
            if (attr == null)
                throw new ArgumentException("EnumDataTypeが指定されていません");
            return attr.EnumType;
        }

        private static bool GetEnumPartsDescription<TValue>(TValue enumValue, Type enumType)
        {
            if (enumValue != null)
            {
                FieldInfo fi = enumType.GetField(enumType.GetEnumName(enumValue));
                PartsDescriptionAttribute[] attributes = (PartsDescriptionAttribute[])fi.GetCustomAttributes(typeof(PartsDescriptionAttribute), false);
                if ((attributes != null) && (attributes.Length > 0))
                {
                    return attributes[0].EnableParts;
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        private static string GetEnumDescription<TValue>(TValue enumValue, Type enumType)
        {
            FieldInfo fi = enumType.GetField(enumType.GetEnumName(enumValue));

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

            if ((attributes != null) && (attributes.Length > 0))
                return attributes[0].Description;
            else
                return enumType.GetEnumName(enumValue);
        }

        public static MvcHtmlString EnumRadioButtonFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            Type enumType = GetEnumTypeFromAttribute(expression);
            IEnumerable<int> values = Enum.GetValues(enumType).Cast<int>();

            var rtnstr = string.Empty;

            foreach (var val in values.Where(x => GetEnumPartsDescription(x, enumType)))
            {
                TagBuilder radioTag = new TagBuilder("input");
                radioTag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
                radioTag.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Radio));

                string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(
                        ExpressionHelper.GetExpressionText(expression));

                radioTag.MergeAttribute("name", fullName, true);

                ModelState modelState;
                string ModelStateValue = null;
                if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
                {
                    if (modelState.Value != null)
                    {
                        ModelStateValue = modelState.Value.ConvertTo(typeof(string), null) as string;
                    }
                }
                bool isChecked = false;
                bool useModelState = false;
                if (ModelStateValue != null)
                {
                    isChecked = String.Equals(ModelStateValue, val.ToString(), StringComparison.Ordinal);
                    useModelState = true;
                }
                if (!useModelState)
                {
                    if (htmlHelper.ViewData.Model != null)
                    {
                        string viewdataval = htmlHelper.ViewData.Eval(fullName).ToString();
                        isChecked = val.ToString() == viewdataval;
                    }
                }
                if (isChecked)
                {
                    radioTag.MergeAttribute("checked", "checked");
                }
                radioTag.MergeAttribute("value", val.ToString(), true);
                // id生成
                string id = fullName + val.ToString();
                radioTag.GenerateId(id);
                // label
                TagBuilder labelTag = new TagBuilder("Label");
                labelTag.InnerHtml = radioTag.ToString();
                labelTag.InnerHtml += GetEnumDescription(val, enumType);
                labelTag.MergeAttribute("for", radioTag.Attributes["id"]);

                rtnstr += labelTag.ToString();
            }



            return new MvcHtmlString(rtnstr);
        }
    }
```


で使い方としては

```csharp
    public class HomeController : Controller
    {
        public ActionResult EnumTest(EnumTestCondition condition)
        {
            return View();
        }
    }

    public class EnumTestCondition
    {
        [System.ComponentModel.DataAnnotations.EnumDataType(typeof(EnumTestEnum))]
        [Required]
        public int? Test { get; set; }

        public enum EnumTestEnum
        {
            [TMFY.MVC.Model.PartsDescription("焼酎ハイボール(レモン)")]
            焼酎ハイボール = 1,
            [TMFY.MVC.Model.PartsDescription(false)]
            トリスハイボール = 2,                        
            メッツコーラ = 3,
            ドクターペッパー = 4,
        }

```

でこんな感じにすると

```html

        @using (Html.BeginForm())
        {
           @Html.EnumRadioButtonFor(m => m.Test)
           @Html.ValidationMessageFor(m => m.Test)
            <input type="submit" value="送信" />
        }
          
```

こんなHtmlが出てくる

```

<form action="/Home/EnumTest" method="post">
<Label for="Test1"><input checked="checked" id="Test1" name="Test" type="radio" value="1"></input>焼酎ハイボール(レモン)</Label>
<Label for="Test3"><input id="Test3" name="Test" type="radio" value="3"></input>メッツコーラ</Label>
<Label for="Test4"><input id="Test4" name="Test" type="radio" value="4"></input>ドクターペッパー</Label>
<span class="field-validation-valid" data-valmsg-for="Test" data-valmsg-replace="true"></span>            
<input type="submit" value="送信" />
</form>

```

ModelStateの値とのからみとか色々操作してますが、基本的にはEnumをRadioButtonのリストにしてるだけです。
ただ、DropDownListと違ってラジオボタンのHtml出力って自由度が高いからその辺上手くないんですよね。
使うプロジェクトのデザインによってHtml生成部分を書き換えたりしてます。
この辺Funcとかにするのが良いのかな。

あと、Modelのプロパティが直接Enumではなくてintなのは
個人的にその方が使いやすい事が多いからです。
ちっちゃいO/Rマッパーを使うことが多いので、そのまま検索条件に使いたい的なですね。。。

おしまい|дﾟ)