使用Linq表达式在扩展方法中有趣(?)

时间:2011-10-29 02:19:33

标签: linq asp.net-mvc-3 c#-4.0 linq-expressions

我写了一个HtmlHelper表达式,我花了很多时间将标题标签放入我的下拉列表中,如下所示:

    public static HtmlString SelectFor<TModel, TProperty, TListItem>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        IEnumerable<TListItem> enumeratedItems,
        string idPropertyName,
        string displayPropertyName,
        string titlePropertyName,
        object htmlAttributes) where TModel : class
    {
        //initialize values
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        var propertyName = metaData.PropertyName;
        var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
        var enumeratedType = typeof(TListItem);

        //build the select tag
        var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
        if (htmlAttributes != null)
        {
            foreach (var kvp in htmlAttributes.GetType().GetProperties()
             .ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
            {
                returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
                 HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
            }
        }
        returnText += ">\n";

        //build the options tags
        foreach (TListItem listItem in enumeratedItems)
        {
            var idValue = enumeratedType.GetProperties()
             .FirstOrDefault(p => p.Name == idPropertyName)
             .GetValue(listItem, null).ToStringOrEmpty();
            var titleValue = enumeratedType.GetProperties()
             .FirstOrDefault(p => p.Name == titlePropertyName)
             .GetValue(listItem, null).ToStringOrEmpty();
            var displayValue = enumeratedType.GetProperties()
             .FirstOrDefault(p => p.Name == displayPropertyName)
             .GetValue(listItem, null).ToStringOrEmpty();
            returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
             HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
            if (idValue == propertyValue)
            {
                returnText += " selected=\"selected\"";
            }
            returnText += string.Format(">{0}</option>\n", displayValue);
        }

        //close the select tag
        returnText += "</select>";
        return new HtmlString(returnText);
    }

...这在游泳方面很有效,但有时候我想要更进一步。我想自定义这个野兽的id,display和title piece,而不必写出html。例如,如果我在模型中有一些类,那么:

public class item
{
    public int itemId { get; set; }
    public string itemName { get; set; }
    public string itemDescription { get; set; }
}

public class model
{
    public IEnumerable<item> items { get; set; }
    public int itemId { get; set; }
}

在我看来,我可以写:

@Html.SelectFor(m => m.itemId, Model.items, "itemId", "itemName", "itemDescription", null)

...我将获得一个带有标题属性等的精彩下拉列表。只要枚举项具有与我想要显示它们完全相同的属性,这就很棒了。但我真正喜欢的是:

@Html.SelectFor(m => m.itemId, Model.items, id=>id.itemId, disp=>disp.itemName, title=>title.itemName + " " + title.itemDescription, null)

...并且在这种情况下,选项上的title属性是itemName属性和itemDescription属性的串联。我承认lambda表达式的元级别,Linq函数让我有点头晕。有人能指出我正确的方向吗?

最终结果对于那些好奇的人,下面的代码让我可以使用lambda表达式完全控制选择列表的ID,Title和DisplayText属性:

    public static HtmlString SelectFor<TModel, TProperty, TListItem>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> forExpression,
        IEnumerable<TListItem> enumeratedItems,
        Attribute<TListItem> idExpression,
        Attribute<TListItem> displayExpression,
        Attribute<TListItem> titleExpression,
        object htmlAttributes,
        bool blankFirstLine) where TModel : class
    {
        //initialize values
        var metaData = ModelMetadata.FromLambdaExpression(forExpression, htmlHelper.ViewData);
        var propertyName = metaData.PropertyName;
        var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
        var enumeratedType = typeof(TListItem);

        //build the select tag
        var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
        if (htmlAttributes != null)
        {
            foreach (var kvp in htmlAttributes.GetType().GetProperties()
             .ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
            {
                returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
                 HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
            }
        }
        returnText += ">\n";

        if (blankFirstLine)
        {
            returnText += "<option value=\"\"></option>";
        }

        //build the options tags
        foreach (TListItem listItem in enumeratedItems)
        {
            var idValue = idExpression(listItem).ToStringOrEmpty();
            var displayValue = displayExpression(listItem).ToStringOrEmpty();
            var titleValue = titleExpression(listItem).ToStringOrEmpty();
            returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
                HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
            if (idValue == propertyValue)
            {
                returnText += " selected=\"selected\"";
            }
            returnText += string.Format(">{0}</option>\n", displayValue);
        }

        //close the select tag
        returnText += "</select>";
        return new HtmlString(returnText);
    }

    public delegate object Attribute<T>(T listItem);

1 个答案:

答案 0 :(得分:5)

如果您不需要各个选项的title属性,您的代码可以简化为:

public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression,
    IEnumerable<TListItem> enumeratedItems,
    Expression<Func<TListItem, TIdProperty>> idProperty,
    Expression<Func<TListItem, TDisplayProperty>> displayProperty,
    object htmlAttributes
) where TModel : class
{
    var id = (idProperty.Body as MemberExpression).Member.Name;
    var display = (displayProperty.Body as MemberExpression).Member.Name;
    var selectList = new SelectList(enumeratedItems, id, display);
    var attributes = new RouteValueDictionary(htmlAttributes);
    return htmlHelper.DropDownListFor(expression, selectList, attributes);
}

并像这样使用:

@Html.SelectFor(
    m => m.itemId, 
    Model.items, 
    id => id.itemId, 
    disp => disp.itemName, 
    null
)

如果您需要title属性,那么您必须实现DropDownList帮助程序手动执行的所有操作,这可能会非常麻烦。以下是所有功能的一小部分示例:

public static class HtmlExtensions
{
    private class MySelectListItem : SelectListItem
    {
        public string Title { get; set; }
    }

    public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        IEnumerable<TListItem> enumeratedItems,
        Expression<Func<TListItem, TIdProperty>> idProperty,
        Expression<Func<TListItem, TDisplayProperty>> displayProperty,
        Func<TListItem, string> titleProperty,
        object htmlAttributes
    ) where TModel : class
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var fullHtmlName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        var select = new TagBuilder("select");
        var compiledDisplayProperty = displayProperty.Compile();
        var compiledIdProperty = idProperty.Compile();
        select.GenerateId(fullHtmlName);
        select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
        select.Attributes["name"] = fullHtmlName;
        var selectedValue = htmlHelper.ViewData.Eval(fullHtmlName);
        var options = 
            from i in enumeratedItems
            select ListItemToOption(
                ItemToSelectItem(i, selectedValue, compiledIdProperty, compiledDisplayProperty, titleProperty)
            );
        select.InnerHtml = string.Join(Environment.NewLine, options);
        return new HtmlString(select.ToString(TagRenderMode.Normal));
    }

    private static MySelectListItem ItemToSelectItem<TListItem, TIdProperty, TDisplayProperty>(TListItem i, object selectedValue, Func<TListItem, TIdProperty> idProperty, Func<TListItem, TDisplayProperty> displayProperty, Func<TListItem, string> titleProperty)
    {
        var value = Convert.ToString(idProperty(i));
        return new MySelectListItem
        {
            Value = value,
            Text = Convert.ToString(displayProperty(i)),
            Title = titleProperty(i),
            Selected = Convert.ToString(selectedValue) == value
        };
    }

    private static string ListItemToOption(MySelectListItem item)
    {
        var builder = new TagBuilder("option");
        builder.Attributes["value"] = item.Value;
        builder.Attributes["title"] = item.Title;
        builder.SetInnerText(item.Text);
        if (item.Selected)
        {
            builder.Attributes["selected"] = "selected";
        }
        return builder.ToString();
    }
}

然后像这样使用:

@Html.SelectFor(
    m => m.itemId, 
    Model.items, 
    id => id.itemId, 
    disp => disp.itemName, 
    title => title.itemName + " " + title.itemDescription, 
    null
)