2011-10-29 9 views
6

me escribió una expresión HtmlHelper yo uso mucho de la hora de poner etiquetas de título en mis listas desplegables de este modo:Diversión con LINQ expresiones en los métodos de extensión

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

... esto funciona a las mil maravillas, pero hay momentos en los que quiero llegar más lejos. Me gustaría personalizar las piezas de id, display y título de esta bestia sin tener que escribir el html. Por ejemplo, si tengo algunas clases en un modelo de este modo:

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

En mi punto de vista que se puede escribir:

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

... y voy a conseguir un buen título desplegable con los atributos, etc. Esto es excelente siempre que los elementos enumerados tengan propiedades exactamente como me gustaría mostrarlos. Pero lo que realmente me gusta hacer es algo como:

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

... y tienen, en este caso, el atributo de título de las opciones de ser una concatenación de la propiedad y la propiedad itemNameitemDescription. Confieso que el meta-nivel de expresiones lambda y las funciones de Linq me marean un poco. ¿Alguien me puede apuntar en la dirección correcta?

RESULTADO FINAL Para aquellos que son curiosos, el siguiente código me da un control completo sobre la identificación, propiedades Título y displayText de la lista de selección utilizando las expresiones 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

NOTA: el ToStringOrEmpty() extensión es mi método que toma un objeto y hace un ToString() en él a menos que sea nulo, en cuyo caso trae una cadena vacía. –

Respuesta

5

Si no es necesario el atributo de título sobre las opciones individuales su código podría simplificarse 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); 
} 

y se utiliza de esta manera:

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

Y si necesita el atributo title, así, tendrá que poner en práctica todo lo que el ayudante DropDownList hace manualmente que podría ser bastante molesto. Aquí está un ejemplo de sólo una pequeña parte de toda la funcionalidad:

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

y luego usar la siguiente manera:

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

La primera opción provista es exactamente lo que trato de evitar. Quiero un control total del código, no quiero enviarlo al código de otra persona ... por la razón especificada, pierdo la capacidad de hacer muchas cosas interesantes. Tu segunda idea es prometedora para lo que estoy tratando de hacer ... Jugaré con ese concepto y veré si puedo lograr que funcione. –

+0

@JeremyHolovacs, realmente no veo qué * cosas interesantes * está perdiendo la capacidad de hacer con los asistentes ASP.NET MVC estándar. Bueno, sí, no puedes agregar un 'título' a esas etiquetas' option', pero ¿es realmente algo que necesitas? Personalmente, no me molestaría en escribir tales ayudantes. Simplemente diseñaría modelos de vista que tengan la propiedad 'IEnumerable ' y los arrojaría al helper 'Html.DropDownListFor' estándar. Hace el trabajo. Si no quiere usar modelos de vista y está tratando de pasar sus modelos de dominio a la vista e intentar escribir ayudantes personalizados, bueno, esa es otra pregunta. –

+0

Es absolutamente.La mayoría de mis requisitos de diseño quieren un nombre en el menú desplegable, pero una descripción en el elemento emergente. Más al punto, no me gusta no tener la * opción *. ¿Por qué conformarse cuando no es necesario? –

Cuestiones relacionadas