2011-10-29 31 views
6

tôi đã viết một biểu thức HtmlHelper tôi sử dụng rất nhiều thời gian để đưa các thẻ tiêu đề vào danh sách thả xuống của tôi như vậy:Fun với LINQ Expressions trong phương pháp khuyến nông

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

... này làm việc thuận buồm xuôi, nhưng có những lúc tôi muốn đi xa hơn. Tôi muốn tùy chỉnh các phần id, hiển thị và tiêu đề của con thú này mà không cần phải viết ra html. Ví dụ, nếu tôi có một số lớp học trong một mô hình như vậy:

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

Theo quan điểm của tôi, tôi có thể viết:

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

... và tôi sẽ nhận được một danh sách lí tốt đẹp với các thuộc tính tiêu đề vv Điều này là tuyệt vời miễn là các mục được liệt kê có các thuộc tính chính xác như tôi muốn hiển thị chúng. Nhưng những gì tôi thực sự muốn làm là một cái gì đó như:

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

... và có, trong trường hợp này, các thuộc tính tiêu đề trên các tùy chọn là một nối của itemName bất động sản và các itemDescription tài sản. Tôi thú nhận siêu mức của các biểu thức lambda và các hàm LINQ đã khiến tôi hơi chóng mặt. Ai đó có thể chỉ cho tôi đi đúng hướng?

CUỐI CÙNG KẾT QUẢ Đối với những người tò mò, đoạn code sau mang lại cho tôi toàn quyền điều khiển ID, tài sản Tiêu đề, và DisplayText sự chọn danh sách bằng cách sử dụng các biểu thức lambda:

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

LƯU Ý: các ToStringOrEmpty() mở rộng là phương pháp của tôi mà phải mất một đối tượng và thực hiện một ToString() trên nó trừ khi nó là null, trong trường hợp nó mang lại một chuỗi rỗng. –

Trả lời

5

Nếu bạn không cần thuộc tính tiêu đề trên tùy chọn cá nhân mã của bạn có thể được đơn giản hóa để:

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

và sử dụng như thế này:

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

Và nếu bạn cần thuộc tính title, tốt, bạn sẽ phải triển khai mọi thứ mà trình trợ giúp DropDownList thực hiện theo cách thủ công. Dưới đây là một ví dụ về chỉ là một phần nhỏ của tất cả các chức năng:

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

và sau đó sử dụng như thế này:

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

Tùy chọn đầu tiên được cung cấp chính xác là những gì tôi đang cố gắng tránh. Tôi muốn kiểm soát toàn bộ mã, không muốn gửi mã cho mã của người khác ... vì lý do được chỉ định, tôi mất khả năng thực hiện nhiều điều thú vị. Ý tưởng thứ hai của bạn có rất nhiều hứa hẹn cho những gì tôi đang cố gắng để làm ... Tôi sẽ chơi với khái niệm đó và xem nếu tôi có thể nhận được rằng để làm việc. –

+0

@ JeremyHolovacs, tôi thực sự không nhìn thấy những gì * những điều thú vị * bạn đang mất khả năng làm với người trợ giúp ASP.NET MVC tiêu chuẩn. Vâng, vâng, bạn không thể thêm một 'title' vào các thẻ' option' nhưng nó thực sự là thứ bạn cần? Cá nhân tôi sẽ không bận tâm để viết những người giúp đỡ như vậy cả. Tôi chỉ đơn giản là sẽ thiết kế các mô hình xem có thuộc tính 'IEnumerable ' và ném nó vào trình trợ giúp 'Html.DropDownListFor' chuẩn. Nó thực hiện công việc. Nếu bạn không muốn sử dụng các mô hình xem và đang cố gắng chuyển các mô hình miền của mình đến chế độ xem và cố gắng viết trình trợ giúp tùy chỉnh, thì đó là một câu hỏi khác. –

+0

Nó hoàn toàn là.Hầu hết các yêu cầu thiết kế của tôi đều muốn có tên trong danh sách thả xuống nhưng mô tả trong di chuột. Thêm vào vấn đề, tôi không thích không có tùy chọn *. Tại sao giải quyết khi bạn không phải làm vậy? –

Các vấn đề liên quan