自定义DropDownListFor Helper为所选元素

时间:2017-08-03 08:32:40

标签: asp.net-mvc asp.net-mvc-4 razor

我正在使用自定义帮助器来创建可以使用HtmlAttribute的select元素。我在@Alexander Puchkov的答案中使用了与我发现的here相同的代码(我将其发布以供参考)。

它工作正常,除了DDL助手加载了一个选项作为选定项目之外,所选项目的值为空/空。 (即在编辑页面上,DDL加载了创建时设置的选项,而不是' - 请选择选项 - '), The highlighted attribute in the picture shows the problem, the value should not be empty, but should show 'Medium'...

因此文本显示正确但元素没有值。关于这个问题来自哪里的任何想法?

以下是帮助者的完整代码:

    /// <summary>
    /// A selectListItem with an extra property to hold HtmlAttributes
    /// <para>Used in conjunction with the sdDDL Helpers</para>
    /// </summary>
    public class SdSelectListItem : SelectListItem
    {
        public object HtmlAttributes { get; set; }
    }

    /// <summary>
    /// Generate DropDownLists with the possibility of styling the 'option' tags of the generated 'select' tag  
    /// </summary>
    public static class SdDdlHelper
    {
       public static MvcHtmlString sdDDLFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                Expression<Func<TModel, TProperty>> expression, IEnumerable<SdSelectListItem> selectList,
                                                                string optionLabel, object htmlAttributes)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);

            return SelectInternal(htmlHelper, metadata, optionLabel, ExpressionHelper.GetExpressionText(expression), selectList,
                false /* allowMultiple */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString sdDDLFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                Expression<Func<TModel, TProperty>> expression, IEnumerable<SdSelectListItem> selectList,
                                                                object htmlAttributes)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);

//-> The below line is my problem (specifically the 'null' param), it set to null if no option label is passed to the method...So if I use this overload, the DDL will load (or re-load) with the default value selected, not the value binded to the property - And if I use the above overload, and set Model.action_priority as the optionLabel, then I get what is shown in the picture...

            return SelectInternal(htmlHelper, metadata, null, ExpressionHelper.GetExpressionText(expression), selectList,
                false /* allowMultiple */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }


        #region internal/private methods

        private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata, string optionLabel, string name,
            IEnumerable<SdSelectListItem> selectList, bool allowMultiple,
            IDictionary<string, object> htmlAttributes)
        {
            string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (String.IsNullOrEmpty(fullName))
                throw new ArgumentException("No name");

            if (selectList == null)
                throw new ArgumentException("No selectlist");

            object defaultValue = (allowMultiple)
                ? htmlHelper.GetModelStateValue(fullName, typeof(string[]))
                : htmlHelper.GetModelStateValue(fullName, typeof(string));

            // If we haven't already used ViewData to get the entire list of items then we need to
            // use the ViewData-supplied value before using the parameter-supplied value.
            if (defaultValue == null)
                defaultValue = htmlHelper.ViewData.Eval(fullName);

            if (defaultValue != null)
            {
                IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
                IEnumerable<string> values = from object value in defaultValues
                                             select Convert.ToString(value, CultureInfo.CurrentCulture);
                HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
                List<SdSelectListItem> newSelectList = new List<SdSelectListItem>();

                foreach (SdSelectListItem item in selectList)
                {
                    item.Selected = (item.Value != null)
                        ? selectedValues.Contains(item.Value)
                        : selectedValues.Contains(item.Text);
                    newSelectList.Add(item);
                }
                selectList = newSelectList;
            }

            // Convert each ListItem to an <option> tag
            StringBuilder listItemBuilder = new StringBuilder();

            // Make optionLabel the first item that gets rendered.
            if (optionLabel != null)
                listItemBuilder.Append(
                    ListItemToOption(new SdSelectListItem()
                    {
                        Text = optionLabel,
                        Value = String.Empty,
                        Selected = false
                    }));

            foreach (SdSelectListItem item in selectList)
            {
                listItemBuilder.Append(ListItemToOption(item));
            }

            TagBuilder tagBuilder = new TagBuilder("select")
            {
                InnerHtml = listItemBuilder.ToString()
            };
            tagBuilder.MergeAttributes(htmlAttributes);
            tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
            tagBuilder.GenerateId(fullName);
            if (allowMultiple)
                tagBuilder.MergeAttribute("multiple", "multiple");

            // If there are any errors for a named field, we add the css attribute.
            System.Web.Mvc.ModelState modelState;
            if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
            {
                if (modelState.Errors.Count > 0)
                {
                    tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
                }
            }

            tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(fullName, metadata));

            return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
        }

        internal static string ListItemToOption(SdSelectListItem item)
        {
            TagBuilder builder = new TagBuilder("option")
            {
                InnerHtml = HttpUtility.HtmlEncode(item.Text)
            };
            if (item.Value != null)
            {
                builder.Attributes["value"] = item.Value;
            }
            if (item.Selected)
            {
                builder.Attributes["selected"] = "selected";
            }
            builder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(item.HtmlAttributes));
            return builder.ToString(TagRenderMode.Normal);
        }

        internal static object GetModelStateValue(this HtmlHelper htmlHelper, string key, Type destinationType)
        {
            System.Web.Mvc.ModelState modelState;
            if (htmlHelper.ViewData.ModelState.TryGetValue(key, out modelState))
            {
                if (modelState.Value != null)
                {
                    return modelState.Value.ConvertTo(destinationType, null /* culture */);
                }
            }
            return null;
        }

        #endregion
    }

}

以下是我在View中调用它的方法(使用Razor):

 @Html.sdDDLFor(x => Model.action_priority, Model.actionPriorityDDL(), Model.action_priority, new
        {
            @id = "_Action_Priority_DDL",
            @class = "form-control"

        })

最后这里是Model.actionPriorityDDL()方法:

public List<SdSelectListItem> actionPriorityDDL()
        {
            action_priority_DDL = new List<SdSelectListItem>();

            action_priority_DDL.Add(new SdSelectListItem
            {
                Value = StringRepository.ActionPriority.high,
                Text = StringRepository.ActionPriority.high,
                HtmlAttributes = new
                {
                    @class = "lbl-action-priority-high"
                }
            }
            );

            action_priority_DDL.Add(new SdSelectListItem
            {
                Value = StringRepository.ActionPriority.medium,
                Text = StringRepository.ActionPriority.medium,
                HtmlAttributes = new
                {
                    @class = "lbl-action-priority-medium"

                }
            }
            );

            action_priority_DDL.Add(new SdSelectListItem
            {
                Value = StringRepository.ActionPriority.low,
                Text = StringRepository.ActionPriority.low,
                HtmlAttributes = new
                {
                    @class = "lbl-action-priority-low"

                }
            }
            );

            action_priority_DDL.Add(new SdSelectListItem
            {
                Value = StringRepository.ActionPriority.psar,
                Text = StringRepository.ActionPriority.psar,
                HtmlAttributes = new
                {
                    @class = "lbl-action-priority-psar"

                }
            }
           );
            return action_priority_DDL;
        }

1 个答案:

答案 0 :(得分:0)

这是由于MVC框架中的前一个错误(http://aspnet.codeplex.com/workitem/8311;可在此处访问:https://web.archive.org/web/20131208041521/http://aspnet.codeplex.com/workitem/8311)在循环中使用DropDownListFor帮助程序时发生的,即模型属性具有索引器(如你的例子,生成的select元素有一个属性name =“actionList [0] .action_priority”)

我通过从https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/Html/SelectExtensions.cs

复制一些代码来解决这个问题

具体来说,在这个方法中

private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata, string optionLabel, string name,
    IEnumerable<ExtendedSelectListItem> selectList, bool allowMultiple,
    IDictionary<string, object> htmlAttributes)

替换此

    if (selectList == null)
        throw new ArgumentException("No selectlist");

使用:

            bool usedViewData = false;

            // If we got a null selectList, try to use ViewData to get the list of items.
            if (selectList == null)
            {
                selectList = htmlHelper.GetSelectData(name);
                usedViewData = true;
}

现在,替换此

    if (defaultValue == null)
        defaultValue = htmlHelper.ViewData.Eval(fullName);

使用:

            if (defaultValue == null && !String.IsNullOrEmpty(name))
            {
                if (!usedViewData)
                {
                    defaultValue = htmlHelper.ViewData.Eval(name);
                }
                else if (metadata != null)
                {
                    defaultValue = metadata.Model;
                }
}

最后,您还需要添加此方法:

        private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
        {
            object o = null;
            if (htmlHelper.ViewData != null)
            {
                o = htmlHelper.ViewData.Eval(name);
            }
            if (o == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.HtmlHelper_MissingSelectData,
                        name,
                        "IEnumerable<SelectListItem>"));
            }
            IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
            if (selectList == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.HtmlHelper_WrongSelectDataType,
                        name,
                        o.GetType().FullName,
                        "IEnumerable<SelectListItem>"));
            }
            return selectList;
}

SelectListItem替换为您的自定义类(例如SdSelectListItemExtendedSelectListItem,或您为其命名的任何内容)