TL; DR: En mi aplicación ASP.NET MVC3, ¿cómo debo implementar una vista que me permita editar detalles de una entidad 'padre' al mismo tiempo que los detalles de un lista de entidades 'infantiles'?Enlazando una lista editable de niños
actualización: Estoy aceptando @torm's answer porque proporciona a link que da una explicación de por qué mi solución actual puede ser tan bueno como se pone. Sin embargo, nos encantaría saber si alguien más tiene alguna alternativa.
He estado buscando y leyendo (consulte la sección 'Referencias' en la parte inferior para conocer algunas de las conclusiones hasta ahora). Sin embargo, todavía siento que hay algo 'mal' con las soluciones que encontré hasta ahora. Me pregunto si alguno de ustedes tiene una respuesta o sugerencia más elegante (o puede explicar por qué esto puede ser 'tan bueno como sea posible'). ¡Gracias de antemano!
tanto, aquí está la disposición:
los modelos:
public class Wishlist
{
public Wishlist() { Wishitems = new List<Wishitem>(); }
public long WishListId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Wishitem> Wishitems { get; set; }
}
public class Wishitem
{
public long WishitemId { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
El controlador:
public class WishlistsController : Controller
{
private SandboxDbContext db = new SandboxDbContext();
/* ... */
public ActionResult Edit(long id)
{
Wishlist wishlist = db.Wishlists.Find(id);
return View(wishlist);
}
[HttpPost]
public ActionResult Edit(Wishlist wishlist)
//OR (see below): Edit(Wishlist wishlist, ICollection<Wishitem> wishitems)
{
if (ModelState.IsValid)
{
db.Entry(wishlist).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(wishlist);
}
/* ... */
}
El Vista: Vistas \ Lista \ Edit.cshtml
@model Sandbox.Models.Wishlist
<h2>Edit</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
<legend>Wishlist</legend>
@Html.HiddenFor(model => model.WishListId)
<div class="editor-label">@Html.LabelFor(model => model.Name)</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<table>
<tr>
<th>
Quantity
</th>
<th>
Name
</th>
</tr>
@for (var itemIndex = 0; itemIndex < Model.Wishitems.Count; itemIndex++)
{
@Html.EditorFor(item => Model.Wishitems.ToList()[itemIndex])
}
</table>
<p>
<input type="submit" value="Save" />
</p>
}
La plantilla del editor: Views \ Shared \ EditorTemplates \ Wishitem.cshtml
@model Sandbox.Models.Wishitem
<tr>
<td>
@Html.HiddenFor(item=>item.WishitemId)
@Html.TextBoxFor(item => item.Quantity)
@Html.ValidationMessageFor(item => item.Quantity)
</td>
<td>
@Html.TextBoxFor(item => item.Name)
@Html.ValidationMessageFor(item => item.Name)
</td>
</tr>
¿Qué está pasando?
La configuración anterior genera una página con los elementos de entrada estándar para el 'padre' Lista modelo:
<input class="text-box single-line" id="Name" name="Name" type="text" value="MyWishlist" />
Para Wishitems los 'niños' en la tabla, obtenemos los elementos de entrada indexados:
<input data-val="true" data-val-number="The field Quantity must be a number." data-val-required="The Quantity field is required." name="[0].Quantity" type="text" value="42" />
<input name="[0].Name" type="text" value="Unicorns" />
Esto lleva a un argumento Wishlist wishlist
POST devuelto con una propiedad .Wishitems
vacía.
La firma alternativa para el manejador de la POST ([HttpPost] public ActionResult Edit(Wishlist wishlist, ICollection<Wishitem> wishitems)
) todavía me consigue un vacío wishlist.Wishitems
, pero me permite acceder al (posiblemente modificado) wishitems
.
En este segundo escenario, puedo hacer algunos enlaces personalizados.Por ejemplo (no el código más elegante que he visto en mi carrera):
[HttpPost]
public ActionResult Edit(Wishlist editedList, ICollection<Wishitem> editedItems)
{
var wishlist = db.Wishlists.Find(editedList.WishListId);
if (wishlist == null) { return HttpNotFound(); }
if (ModelState.IsValid)
{
UpdateModel(wishlist);
foreach (var editedItem in editedItems)
{
var wishitem = wishlist.Wishitems.Where(wi => wi.WishitemId == editedItem.WishitemId).Single();
if (wishitem != null)
{
wishitem.Name = editedItem.Name;
wishitem.Quantity = editedItem.Quantity;
}
}
db.SaveChanges();
return View(wishlist);
}
else
{
editedList.Wishitems = editedItems;
return View(editedList);
}
}
Lista de Deseos
Me gustaría que hubiera una manera para mí para obtener todos los datos enviados en un solo objeto estructurado , por ejemplo:
[HttpPost]
public ActionResult Edit(Wishlist wishlist) { /* ...Save the wishlist... */ }
con wishlist.Wishitems
lleno de los elementos (posiblemente modificadas)
o una más el Una manera elegante de manejar la fusión de los datos, si mi controlador debe recibirlos por separado. Algo así como
[HttpPost]
public ActionResult Edit(Wishlist editedList, ICollection<Wishitem> editedItems)
{
var wishlist = db.Wishlists.Find(editedList.WishListId);
if (wishlist == null) { return HttpNotFound(); }
if (ModelState.IsValid)
{
UpdateModel(wishlist);
/* and now wishlist.Wishitems has been updated with the data from the Form (aka: editedItems) */
db.SaveChanges();
return View(wishlist);
}
/* ...Etc etc... */
}
pistas, consejos, pensamientos?
Notas:
- Este es un ejemplo recinto de seguridad. La aplicación real en la que estoy trabajando es bastante diferente, no tiene nada que ver con el dominio expuesto en Sandbox.
- No estoy usando 'ViewModels' en el ejemplo, porque -hasta el momento- no parecen ser parte de la respuesta. Si son necesarios, ciertamente los presentaré (y en la aplicación real en la que estoy trabajando ya los estamos usando).
- De manera similar, el repositorio se abstrae mediante la clase simple SandboxDbContext en este ejemplo, pero probablemente se reemplazará por un patrón genérico de Repositorio y Unidad de trabajo en la aplicación real.
- La aplicación recinto de seguridad se construye usando:
- Visual Web Developer 2010 Express
- de revisión para Microsoft Visual Web Developer 2010 Express - ENU (KB2547352)
- de revisión para Microsoft Visual Web Developer 2010 Express - ENU (KB2548139)
- Microsoft Visual web Developer 2010 Express - Servicio ENU pack 1 (KB983509)
- .NET Framework 4.0.30319 SP1Rel
- ASP.NET MVC3
- sintaxis Razor para las Vistas
- Código-Primera aproximación
- Entity Framework 4.2.0.0
- Visual Web Developer 2010 Express
- Sandbox se construye la orientación.NET Framework 4
Referencias:
"Getting Started with ASP.NET MVC3" cubre los conceptos básicos, pero no se ocupa de las relaciones modelo
"Getting Started with EF using MVC" an-aplicación-asp-NET MVC En particular Part 6 muestra cómo lidiar con algunas de las relaciones entre los modelos. Sin embargo, este tutorial utiliza un argumento
FormCollection
para su controlador POST, en lugar del enlace de modelo automático. En otras palabras: [HttpPost] ActionResult pública Editar (int id, FormCollection FormCollection) En lugar de algo en la línea de [HttpPost] Editar ActionResult pública (InstructorAndCoursesViewModel viewmodel) Además, la lista de los cursos asociados a un instructor dada está representado (en la interfaz de usuario) como un conjunto de casillas de verificación con el mismo nombre (lo que lleva a un argumentostring[]
para el controlador POST), no es exactamente el mismo escenario que estoy viendo."Editing a variable length list, ASP.NET MVC2-style" Basado en MVC2 (Así que me pregunto si todavía se describe la mejor opción ahora que tenemos MVC3). Es cierto que todavía no he tenido que ocuparme de las inserciones y/o eliminación de los modelos Children de la lista. Además, esta solución:
- se basa en un código personalizado (BeginCollectionItem) - que está bien si es necesario (pero sigue siendo necesario en MVC3?)
- se encarga de la lista como una colección de libre de pie, en lugar de una propiedad de un modelo de ajuste, en otras palabras, existe un modelo "Conjunto de regalo" circundante (equivalente al modelo de lista de elementos principales en mi ejemplo), aunque no sé si la introducción de un modelo primario explícito invalida esta solución o no. El post de
"ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries" de Scott Hanselman es uno de los hijos de referencia más citado el tema de unirse a las listas de aplicaciones MVC. Sin embargo, simplemente describe las convenciones de nomenclatura adoptadas por el marco y las utiliza para generar objetos que coinciden con su método de acción (observe cómo el artículo no tiene ningún ejemplo de generar una página que luego envíe datos a una de las acciones descritas). Esta es una gran información si tenemos que generar el HTML nosotros mismos. ¿Tenemos que hacerlo?
"Model Binding To A List" Otra referencia superior, por Phil Haack. Tiene la misma información que la publicación anterior de Hansleman, pero también nos muestra que podemos usar HtmlHelpers dentro de un bucle (
for (int i = 0; i < 3; i++) { Html.TextBoxFor(m => m[i].Title) }
) o en una Plantilla de editor (Html.EditorFor(m=>m[i])
). Sin embargo, utilizando este enfoque, el HTML generado por la plantilla del editor no incluiría ningún prefijo específico (por ejemplo: los nombres y los identificadores de los elementos de entrada estarían en el formato[index].FieldName
como:[0].Quantity
, o[1].Name
). Esto puede o no ser crítico en el ejemplo, pero probablemente sea un problema en mi aplicación real, donde diferentes listas "paralelas" de niños pueden aparecer en la misma vista.
que necesita para enmarcar su pregunta que puede ser fácilmente legible – user1006544