自定义DropDownListFor Helper会为所选元素加载一个空值 [英] Custom DropDownListFor Helper loads an empty value for the selected element

查看:76
本文介绍了自定义DropDownListFor Helper会为所选元素加载一个空值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是自定义帮助程序,以创建可以使用HtmlAttribute的select元素.我正在使用与此处相同的代码,在@Alexander Puchkov答案中(我将发布以作参考).

I am using a custom helper, to create a select element that can take HtmlAttribute. I am using the same code as I found here, in @Alexander Puchkov answer (which I'll post for reference).

它工作正常,除了在DDL帮助器中加载选项之一作为选定项时,选定项的值为null/empty.(即,在DDL的编辑页面上,它会加载创建时设置的选项,而不是-请选择选项-"),图片中突出显示的属性显示了问题,该值不应为空,而应显示'Medium'...

It's working fine, apart when the DDL helper is loaded with one of the option as the selected item, then the value of the selected item is null/empty. (i.e. on an edit page the DDL loads up with the option that was set on creation, rather than the '-Please select option-'), The highlighted attribute in the picture shows the problem, the value should not be empty, but should show 'Medium'...

因此,文本可以正确显示,但是元素没有任何值.关于此问题的来源有何想法?

So the text is display correctly but the element has no value. Any ideas on where this issue comes from?

这是帮助程序的完整代码:

Here is the full code of the helper:

    /// <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调用它的方式:

Here is how I call it in the View (using Razor):

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

        })

最后是Model.actionPriorityDDL()方法:

And finally here is the Model.actionPriorityDDL() method:

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;
        }

推荐答案

这是由于MVC框架中的一个先前错误(

This is caused by a former bug in MVC framework (http://aspnet.codeplex.com/workitem/8311; accessible here: https://web.archive.org/web/20131208041521/http://aspnet.codeplex.com/workitem/8311) that happens when using the DropDownListFor helper within a loop, i.e. the model property has an indexer (like in your example, where the generated select element has an attribute name="actionList[0].action_priority")

我通过从此处具体来说,用这种方法

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;
                }
}

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

Finally, you also need to add this method:

        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 (例如 SdSelectListItem ExtendedSelectListItem 或您命名的任何内容)

replacing SelectListItem with your custom class (e.g. SdSelectListItem, or ExtendedSelectListItem , or whatever you named it )

这篇关于自定义DropDownListFor Helper会为所选元素加载一个空值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆