asp.net - mvc - html dropdownlist selected value
Plantilla de Editor MVC2 para DropDownList (3)
He pasado la mayor parte de la semana pasada hasta las rodillas en la nueva funcionalidad de plantillas integrada en MVC2. Me costó mucho intentar que una plantilla DropDownList funcionara. El mayor problema que he estado trabajando para resolver es cómo obtener los datos de origen para la lista desplegable en la plantilla. Vi muchos ejemplos en los que puede colocar los datos de origen en el diccionario de ViewData (ViewData ["DropDownSourceValuesKey"]) y luego recuperarlos en la propia plantilla (var sourceValues = ViewData ["DropDownSourceValuesKey"];) Esto funciona, pero lo hice. No me gusta tener una cuerda tonta como el pin de enlace para hacer que esto funcione.
A continuación, se encuentra un enfoque que se me ocurrió y quería obtener opiniones sobre este enfoque:
Aquí están mis objetivos de diseño:
- El modelo de vista debe contener los datos de origen para la lista desplegable
- Limitar cuerdas tontas
- No usar el diccionario de ViewData
- El controlador es responsable de completar la propiedad con los datos de origen de la lista desplegable
Aquí está mi modelo de vista:
public class CustomerViewModel
{
[ScaffoldColumn(false)]
public String CustomerCode{ get; set; }
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "CustomerCode"]
[DisplayName("Customer Code")]
public IEnumerable<SelectListItem> CustomerCodeList { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String PhoneNumber { get; set; }
public String Address1 { get; set; }
public String Address2 { get; set; }
public String City { get; set; }
public String State { get; set; }
public String Zip { get; set; }
}
My View Model tiene una propiedad CustomerCode, que es un valor que el usuario selecciona de una lista de valores. Tengo una propiedad CustomerCodeList que es una lista de posibles valores de Código de cliente y es el origen de una lista desplegable. He creado un atributo DropDownList con un DropDownListTargetProperty. DropDownListTargetProperty apunta a la propiedad que se completará en función de la selección del usuario del menú desplegable generado (en este caso, la propiedad CustomerCode).
Observe que la propiedad CustomerCode tiene [ScaffoldColumn (false)] que obliga al generador a omitir el campo en la salida generada.
Mi archivo DropDownList.ascx generará un elemento de formulario de lista desplegable con los datos de origen de la propiedad CustomerCodeList. La lista desplegable generada utilizará el valor de DropDownListTargetProperty del atributo DropDownList como los atributos Id y Name del elemento de formulario Seleccionar. Entonces el código generado se verá así:
<select id="CustomerCode" name="CustomerCode">
<option>...
</select>
Esto funciona muy bien porque cuando se envía el formulario, MVC llenará la propiedad de destino con el valor seleccionado de la lista desplegable porque el nombre de la lista desplegable generada ES la propiedad de destino. Lo visualizo un poco, ya que la propiedad CustomerCodeList es una extensión de las clases de la propiedad CustomerCode. He acoplado los datos de origen a la propiedad.
Aquí está mi código para el controlador:
public ActionResult Create()
{
//retrieve CustomerCodes from a datasource of your choosing
List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList();
CustomerViewModel viewModel= new CustomerViewModel();
viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable();
return View(viewModel);
}
Aquí está mi código para el atributo DropDownListAttribute:
namespace AutoForm.Attributes
{
public class DropDownListAttribute : Attribute
{
public String DropDownListTargetProperty { get; set; }
}
}
Aquí está mi código para la plantilla (DropDownList.ascx):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%@ Import Namespace="AutoForm.Attributes"%>
<script runat="server">
DropDownListAttribute GetDropDownListAttribute()
{
var dropDownListAttribute = new DropDownListAttribute();
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute"))
{
dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"];
}
return dropDownListAttribute;
}
</script>
<% DropDownListAttribute attribute = GetDropDownListAttribute();%>
<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>">
<% foreach(SelectListItem item in ViewData.Model)
{%>
<% if (item.Selected == true) {%>
<option value="<%= item.Value %>" selected="true"><%= item.Text %></option>
<% } %>
<% else {%>
<option value="<%= item.Value %>"><%= item.Text %></option>
<% } %>
<% } %>
</select>
Intenté usar el ayudante Html.DropDownList, pero no me permitía cambiar los atributos Id y Name del elemento Select generado.
NOTA: debe anular el método CreateMetadata del DataAnnotationsModelMetadataProvider para el DropDownListAttribute. Aquí está el código para eso:
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault();
if (additionalValues != null)
{
metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues);
}
return metadata;
}
}
Luego tiene que hacer una llamada al nuevo MetadataProvider en Application_Start of Global.asax.cs:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new MetadataProvider();
}
Bueno, espero que esto tenga sentido y espero que este enfoque pueda ahorrarle algo de tiempo. Me gustaría algún comentario sobre este enfoque, por favor. ¿Hay un mejor enfoque?
Creo que encontré una solución para que funcione al usar Html.EditorForModel (); Cuando se usa EditorForModel (), MVC usa Object.ascx para recorrer todas las propiedades del modelo y llama a la plantilla correspondiente para cada propiedad en el modelo. ASP.Net MVC fuera de la caja tiene Object.ascx en código, pero puede crear su propio Object.ascx. Simplemente cree una subcarpeta EditorTemplates en su carpeta Vista compartida. Crear un archivo Object.ascx allí. (lea esta publicación para obtener más información: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html )
Aquí está mi Object.ascx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="WebAppSolutions.Helpers" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText%>
<% }
else { %>
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
<% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%>
<% if (prop.HideSurroundingHtml) { %>
<%= Html.Editor(htmlFieldName)%>
<% }
else { %>
<div id="<%= htmlFieldName %>Container" class="editor-field">
<% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %>
<%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%>
<% } %>
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
<%= Html.ValidationMessage(prop.PropertyName, "*") %>
</div>
<% } %>
<% } %>
<%}%>
Tengo algunos códigos de custome en mi WebAppSolutions.Helpers para HtmlFieldNameFor y HtmlDisplayName. Estos ayudantes recuperan datos de atributos aplicados a propiedades en el modelo de vista.
public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return GetHtmlFieldName(modelMetaData, propertyName);
}
public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
return modelMetaData.DisplayName ?? propertyName;
}
private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName)
{
ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData);
return modelMetaData;
}
private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName)
{
PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);
return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName;
}
La clave para que esto funcione con EditorModelFor () es esta (debería ser la línea 20 o más en Object.ascx arriba):
<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
prop.PropertyName es la propiedad en ViewModel que contiene la lista de datos que se convertirá en DropDownList. htmlFieldName es el nombre de la propiedad que está oculta y que la propiedad DropDownList está reemplazando. ¿Tener sentido?
Espero que esto te ayude.
Este es mi enfoque de este post en Code Project:
Una plantilla de editor para todas las DropDownLists en ASP.Net MVC
Perfecto. Esto es lo que estoy buscando. ¡Gracias!
Pero tu modelo de ejemplo es un modelo simple. ¿Qué tal un modelo de vista complejo como
public class MaintainServicePackageViewModel
{
public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; }
public ServicePackageWithOwnerName CurrentServicePackage { get; set; }
public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; }
}
public class ServicePackageWithOwnerName : ServicePackage
{
[UIHint("DropDownList")]
[DropDownList(DropDownListTargetProperty = "Owner")]
[DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")]
public IEnumerable<SelectListItem> OwnerName { get; set; }
}
El nombre de propietario se establece en una lista desplegable, pero no es un elemento directo del modelo de vista, sino que es un elemento secundario de ServicePackageWithOwnerName, que es el elemento del modelo de vista. En tal condición, no hay manera de establecer el valor de OwnerName en el controlador, ¿cómo solucionarlo? ¡Apreciar!
Saludos
Jack