编写一个简短的 HtmlHelper DropDownList 重载方法的类型问题

Type issue writing a short HtmlHelper DropDownList overloaded method

我正在尝试重载 DropDownListFor HtmlHelper 方法。

通常,参数 'htmlAttributes' 是一个匿名对象。如果 htmlAttributes 还不是 RouteValueDictionary,我会将其转换为这种类型,以便根据需要修改集合,基于我将添加的自定义逻辑,例如根据值处理禁用控件"disabled" 参数值,仅作为一个示例。

    public static MvcHtmlString DropDownListFor<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper, 
            Expression<Func<TModel, TProperty>> expression, 
            IEnumerable<SelectListItem> selectList, 
            string optionLabel, 
            object htmlAttributes,
            bool disabled)
    {
        if (!(htmlAttributes is RouteValueDictionary))
        {
            htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
        }
        else
        {
            htmlAttributes = (RouteValueDictionary)htmlAttributes;
        }

        if (disabled)
        {
            //manipulate the htmlAttributes collection here                     
        }
        return htmlHelper.DropDownListFor(expression, selectList, optionLabel, htmlAttributes);
    }

我在最后一行调用 "native" System.Web.Mvc.Html.DropDownListFor 时遇到了问题 运行。当它期望一个匿名对象时,它没有正确处理 html 类型为 "RouteAttributes" 的属性。结果,我得到的 HTML 没有正确地将 routingAttributes 集合的 name/value html 属性转换为 nanme/value HTML 属性对。我明白了(注意 Keys 和 Values 属性):

例如,当我这样调用它时:

@Html.DropDownListFor(model => model.FrequencyCode, Model.FrequencyCodes.ToListOfSelectListItem(Model.FrequencyCode), "", new { @class = "form-control" }, true)

我明白了:

<input data-val="true" data-val-required="Frequency is required" id="FrequencyCode" name="FrequencyCode" type="hidden" value="" />
<select Count="1" Keys="System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]" Values="System.Collections.Generic.Dictionary`2+ValueCollection[System.String,System.Object]" id="FrequencyCode" name="FrequencyCode">
<option value=""></option>
<option value="A">ANNUAL</option>
<option value="M">MONTHLY</option>
<option value="Q">QUARTERLY</option>
</select>

似乎解决此问题的一种方法是将 RouteDictionary 类型的对象转换回匿名对象。我该怎么做呢?有没有更好的方法?

解决此问题的最佳方法是使用多个重载,就像内置 HtmlHelper 方法一样(请参阅 source code 了解 DropDownListFor() 方法)。

public static MvcHtmlString DropDownListFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TProperty>> expression, 
        IEnumerable<SelectListItem> selectList, 
        string optionLabel, 
        IDictionary<string, object> htmlAttributes,
        bool disabled)

public static MvcHtmlString DropDownListFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TProperty>> expression, 
        IEnumerable<SelectListItem> selectList, 
        string optionLabel, 
        object htmlAttributes,
        bool disabled)

但是,生成一个禁用的 <select> 元素没有多大意义 - 您生成了很多额外的不必要的 html,并且禁用的控件不会在那里提交值,因此模型绑定可能会失败, 如果该方法绑定到值类型的 属性 或具有验证属性,则 ModelState 将无效。更好的方法是仅显示选定的文本值和隐藏的输入(假设您想要 post 该值)。

public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes, bool disabled)
{
    if (disabled)
    {
        StringBuilder html = new StringBuilder();
        // add a hidden input
        html.Append(htmlHelper.HiddenFor(expression).ToString());
        // Get the display text
        ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        string value = metaData.Model.ToString();
        var displayText = selectList.Where(x => x.Value == value).Select(x => x.Text).FirstOrDefault();
        TagBuilder div = new TagBuilder("div");
        div.AddCssClass("readonly");
        div.InnerHtml = displayText;
        html.Append(div.ToString());
        return MvcHtmlString.Create(html.ToString());
    }
    return htmlHelper.DropDownListFor(expression, selectList, optionLabel, htmlAttributes);
}

然后使用 css 将 .readonly 样式设置为看起来像 <input /><select> 元素(如果这是您想要的)。