Códigos de muestra - Ejemplos MSDN. ModelBinderProvider para tipos genéricos
En un post anterior mostré cómo podíamos crear y registrar un ModelBinder personalizado en una aplicación MVC (ASP.NET MVC. Crear un ModelBinder personalizado). Sin embargo, el tipo de datos que indicamos a MVC a la hora de registrar el ModelBinder debe ser un tipo de datos específico, no podemos indicar un tipo genérico.
Entonces, ¿cómo podemos crear un ModelBinder para un tipo de datos genérico que ASP.NET MVC no es capaz de enlazar por defecto?
Por suerte ASP.NET MVC nos permite también la opción de registrar proveedores de ModelBinders o ModelBinderProviders los cuales se encargan de crear las instancias de ModelBinders para ciertos tipos de datos.
En este artículo mostraré cómo crear y registrar un ModelBinderProvider para el tipo genérico KeyValuePair<TKey, TValue>. Veremos porqué ASP.NET MVC no es capaz de enlazarlo por defecto, cómo crear el ModelBinder para el tipo y cómo podemos indicarle a MVC que debe utilizar nuestro ModelBinder para enlazar estos tipos de datos utilizando un ModelBinderProvider.
Creando el escenario
Para crear el ejemplo empezaremos creando un nuevo proyecto GenericModelBinderProvider ASP.NET vacío añadiendo las librerías de MVC:
A continuación crearé una clase Item en la carpeta Models que utilizaré como modelo en el ejemplo:
public class Item { [DisplayName("Código")] public string Code { get; set; } [DisplayName("Nombre")] public string Name { get; set; } [DisplayName("Características")] public List<KeyValuePair<string, string>> Characteristics { get; set; } [DisplayName("Medidas")] public List<KeyValuePair<string, double>> Measures { get; set; } }
Public Class Item <DisplayName("Código")> Public Property Code As String <DisplayName("Nombre")> Public Property Name As String <DisplayName("Características")> Public Property Characteristics As List(Of KeyValuePair(Of String, String)) <DisplayName("Medidas")> Public Property Measures As List(Of KeyValuePair(Of String, Double)) End Class
La clase Item tiene dos propiedades de tipo String (Code y Name) que, por tratarse de tipos básicos de .NET, MVC será capaz de enlazar automáticamente, y dos propiedades cuyo valor son listas de objetos KeyValuePair y que, como veremos, MVC no será capaz de enlazar.
Para mantener el ejemplo lo más simple posible voy a crear dos vistas, una que mostrará un formulario web para editar un elemento Item y una segunda que mostrará los datos de un objeto Item.
En primer lugar crearé un controlador HomeController con dos acciones Index. La primera responderá al método GET y creará una instancia vacía de un objeto Item (al que le añadiré un par de elementos para editar en las listas de las propiedades Características y Medidas) que enviará a la vista con el formulario de edición.
La segunda responderá al método POST y recibirá el elemento Item del formulario de edición enviándolo a una segunda vista Result que se encargará de mostrar el resultado en el navegador.
[HttpGet] public ActionResult Index() { var characterisitics = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("Color", ""), new KeyValuePair<string, string>("Tamaño", "") }; var measures = new List<KeyValuePair<string, double>> { new KeyValuePair<string, double>("Altura", 0), new KeyValuePair<string, double>("Ancho", 0) }; var item = new Item { Characteristics = characterisitics, Measures = measures }; return View(item); } [HttpPost] public ActionResult Index(Item item) { return View("Result", item); }
' GET: Home Function Index() As ActionResult Dim characteristics As New List(Of KeyValuePair(Of String, String)) From { new KeyValuePair(Of String,String)("Color", ""), new KeyValuePair(Of String, String)("Tamaño", "") } Dim measures As New List(Of KeyValuePair(Of String, Double)) From { New KeyValuePair(Of String,Double)("Altura", 0), New KeyValuePair(Of String,Double)("Ancho", 0) } Dim item As New Item With{ .Characteristics= characteristics, .Measures = measures } Return View(item) End Function <HttpPost()> Function Index(item As Item) As ActionResult Return View("Result", item) End Function
Las vistas serán igualmente muy simples, ambas utilizarán la clase Item como modelo. La vista Index tendrá un formulario en el que añadiré un editor para cada propiedad de la clase Item con el método de extensión EditorFor, mientras que la vista Result mostrará la información de estas mismas propiedades utilizando el método DisplayFor:
@model GenericModelBinderProvider.Models.Item @{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Item</title> </head> <body> <div> @using (Html.BeginForm()) { @Html.LabelFor(m => m.Code) @: @Html.EditorFor(m => m.Code) <br/><br/> @Html.LabelFor(m => m.Name) @: @Html.EditorFor(m => m.Name) <br/><br/> @Html.LabelFor(m => m.Characteristics) <br/> @Html.EditorFor(m => m.Characteristics) <br/><br/> @Html.LabelFor(m => m.Measures) <br/> @Html.EditorFor(m => m.Measures) <br/><br/> <input type="submit" value="Validar" /> } </div> </body> </html>
@ModelType GenericModelBinderProvider.Item @Code Layout = Nothing End Code <!DOCTYPE html> <html> <head> <title>Index</title> </head> <body> <div> @Using (Html.BeginForm()) @Html.LabelFor(Function(m) m.Code) @: @Html.EditorFor(Function(m) m.Code) @:<br /><br /> @Html.LabelFor(Function(m) m.Name) @: @Html.EditorFor(Function(m) m.Name) @:<br /><br /> @Html.LabelFor(Function(m) m.Characteristics) @:<br /> @Html.EditorFor(Function(m) m.Characteristics) @:<br /><br /> @Html.LabelFor(Function(m) m.Measures) @:<br /> @Html.EditorFor(Function(m) m.Measures) @:<br /> @:<input type="submit" value="Validar" /> End Using </div> </body> </html>
@model GenericModelBinderProvider.Models.Item @{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Result</title> </head> <body> <div> @Html.LabelFor(m => m.Code) @Html.DisplayFor(m => m.Code) <br /><br /> @Html.LabelFor(m => m.Name) @Html.DisplayFor(m => m.Name) <br /><br /> @Html.LabelFor(m => m.Characteristics) <br /> @Html.DisplayFor(m => m.Characteristics) <br /><br /> @Html.LabelFor(m => m.Measures) <br /> @Html.DisplayFor(m => m.Measures) </div> </body> </html>
@ModelType GenericModelBinderProvider.Item @Code Layout = Nothing End Code <!DOCTYPE html> <html> <head> <title>Result</title> </head> <body> <div> @Html.LabelFor(Function(m) m.Code) @Html.DisplayFor(Function(m) m.Code) <br /><br /> @Html.LabelFor(Function(m) m.Name) @Html.DisplayFor(Function(m) m.Name) <br /><br /> @Html.LabelFor(Function(m) m.Characteristics) <br /> @Html.DisplayFor(Function(m) m.Characteristics) <br /><br /> @Html.LabelFor(Function(m) m.Measures) <br /> @Html.DisplayFor(Function(m) m.Measures) </div> </body> </html>
Ya tenemos el ejemplo casi listo. Si arrancamos el sitio web veremos que se muestra el formulario para editar el elemento Item:
Sin embargo podemos ver que no se han generado controles de edición para los elementos de Características y Medidas. Esto es porque MVC no tiene un editor predeterminado para los elementos KeyValuePair. Para solucionarlo crearé dos vistas parciales (Characteristics y Measures) que utilizaré como editores de las propiedades. Las vistas las crearé en una carpeta EditorTemplates dentro de la carpeta del proyecto Views/Home.
@model List<KeyValuePair<string, string>> @for (int i = 0; i < Model.Count; i++) { @Html.EditorFor(m => m[i].Key, new { htmlAttributes= new { readOnly = true } }) @Html.EditorFor(m => m[i].Value) <br /> }
@ModelType List(Of KeyValuePair(Of String, String)) @For i = 0 To Model.Count - 1 @Html.EditorFor(Function(m) m(i).Key, New With {.htmlAttributes = New With {.readOnly = True}}) @Html.EditorFor(Function(m) m(i).Value) @:<br /> Next
@model List<KeyValuePair<string, double>> @for (int i = 0; i < Model.Count; i++) { @Html.EditorFor(m => m[i].Key, new { htmlAttributes= new { readOnly = true } }) @Html.EditorFor(m => m[i].Value) <br /> }
@ModelType List(Of KeyValuePair(Of String, Double)) @For i = 0 To Model.Count - 1 @Html.EditorFor(Function(m) m(i).Key, New With {.htmlAttributes = New With {.readOnly = True}}) @Html.EditorFor(Function(m) m(i).Value) @:<br /> Next
Los editores simplemente crean controles de edición para las propiedades Key y Value de cada elemento, permitiendo editar únicamente el de la propiedad Value.
Para indicarle a MVC que debe utilizar estos editores para las propiedades Characteristics y Measures utilizaremos el atributo UiHintAttribute en la definición de la clase Item:
[DisplayName("Características")] [UIHint("Characteristics")] public List<KeyValuePair<string, string>> Characteristics { get; set; } [DisplayName("Medidas")] [UIHint("Measures")] public List<KeyValuePair<string, double>> Measures { get; set; }
<DisplayName("Características")> <UiHint("Characteristics")> Public Property Characteristics As List(Of KeyValuePair(Of String, String)) <DisplayName("Medidas")> <UIHint("Measures")> Public Property Measures As List(Of KeyValuePair(Of String, Double))
Si volvemos a arrancar la aplicación veremos que ya podemos editar los elementos de estas propiedades:
Sin embargo su pulsamos el botón "Validar" para enviar el contenido del formulario al controlador veremos que en la vista que muestra el resultado no se muestran los valores de estos elementos:
El motivo es que MVC no es capaz de realizar el enlace de datos de los elementos KeyValuePair. En el siguiente artículo veremos cómo podemos solucionar esta situación.
Artículo siguiente:
ASP.NET MVC. ModelBinderProvider para Tipos Genéricos (y II). KeyValuePair DataBinding
El código completo tanto en C# como en Visual Basic .NET está disponible en:
Códigos de muestra - Ejemplos MSDN. ModelBinderProvider para tipos genéricos
No hay comentarios:
Publicar un comentario