しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC でラジオボタン周りを良い感じに扱う

ラジオボタンを作成する Html.RadioButtonFor の使えない子っぷりが半端ないです。そのあたりの解説はみそ先生の記事を読んでください。

ちょっと異色のRadioButtonHelper - miso_soup3

id がデフォルトだと同じになってしまったり、表示させたい個数分だけ Html.RadioButtonFor を書いたりめんどくさいですね。

そして ASP.NET MVC でドロップダウンリスト周りを良い感じに扱う - しばやん雑記 で DisplaySelectFor というヘルパーを作成したので是非とも SelectList を使いたい…!という訳でヘルパーを作成してみました。やってることは SelectList からラジオボタンを作ってるだけです。

public static class RadioButtonHelpers
{
    public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Func<dynamic, HelperResult> label = null)
    {
        var name = ExpressionHelper.GetExpressionText(expression);

        var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        var value = metadata.Model.ToString();

        var result = new StringBuilder();

        var selectList = (IEnumerable<SelectListItem>)htmlHelper.ViewData.Eval(name);

        foreach (var selectListItem in selectList)
        {
            var id = fullName + "-" + selectListItem.Value;

            var tagBuilder = new TagBuilder("input");

            tagBuilder.MergeAttribute("id", id);
            tagBuilder.MergeAttribute("type", "radio");
            tagBuilder.MergeAttribute("name", fullName, true);
            tagBuilder.MergeAttribute("value", selectListItem.Value);

            if (selectListItem.Value == value)
            {
                tagBuilder.MergeAttribute("checked", "checked");
            }

            result.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

            if (label != null)
            {
                var helperResult = label(new RadioButtonLabelItem { Id = id, Name = selectListItem.Text });

                result.Append(helperResult.ToHtmlString());
            }
        }

        return MvcHtmlString.Create(result.ToString());
    }

    public class RadioButtonLabelItem
    {
        public string Id { get; set; }

        public string Name { get; set; }
    }
}

実装としては非常にシンプルですが、標準のヘルパーでは id が同じになってしまう問題があったのでタグの出力も自前で行いました。id は "プロパティ名-値" としているので被らないようになっています。

今回は引数として Templated Razor Delegate を取るようにして、ラベルを追加可能にしました。SelectList は表示テキストと値を持っているのでラベルぐらい自動で作れますが、今回はテンプレートとして切り出すことにしました。

実際に使う時は以下のように書きます。

@Html.RadioButtonListFor(model => model.TypeId, @<label for="@item.Id">@item.Name</label>)

テンプレートに渡される item オブジェクトには Id と Name というプロパティがあるので、それを使って良い感じにラベルを関連付けることが出来ます。

これで自分的には HTML ヘルパーだけで良い感じにドロップダウンリストとラジオボタンを書けるようになって満足です。