In questo esempio ASP.Net MVC 4 programma Ho un utente che specifica i dettagli di una corsa di cavalli. La razza ha un nome e una lista di cavalli coinvolti. Ogni cavallo ha un nome e un'età.Come posso ottenere i messaggi di convalida per il rendering sulle proprietà della raccolta quando si utilizzano nuovi indici guid ogni volta?
Il modulo utilizza ajax e javascript per consentire alla persona di aggiungere e cancellare campi di input cavallo al volo, che viene quindi inviato tutto in una volta quando viene premuto il pulsante di invio.
Per semplificare questo processo per me, sto utilizzando un html helper effettuato da Matt Lunn.
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string htmlFieldName = null) where TModel : class
{
var items = expression.Compile()(html.ViewData.Model);
var sb = new StringBuilder();
if (String.IsNullOrEmpty(htmlFieldName))
{
var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
}
foreach (var item in items)
{
var dummy = new { Item = item };
var guid = Guid.NewGuid().ToString();
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
sb.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
}
return new MvcHtmlString(sb.ToString());
}
Mentre io non capisco tutti i dettagli (si prega di leggere il post sul blog), so che cambia i valori di indice in GUID piuttosto che interi sequenziali. Ciò mi consente di eliminare gli elementi nel mezzo dell'elenco senza dover ricalcolare gli indici.
Ecco il resto del mio codice per la mia MCVE
HomeController.cs
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var model = new Race();
//start with one already filled in
model.HorsesInRace.Add(new Horse() { Name = "Scooby", Age = 10 });
return View(model);
}
[HttpPost]
public ActionResult Index(Race postedModel)
{
if (ModelState.IsValid)
//model is valid, redirect to another page
return RedirectToAction("ViewHorseListing");
else
//model is not valid, show the page again with validation errors
return View(postedModel);
}
[HttpGet]
public ActionResult AjaxMakeHorseEntry()
{
//new blank horse for ajax call
var model = new List<Horse>() { new Horse() };
return PartialView(model);
}
}
Models.cs
public class Race
{
public Race() { HorsesInRace = new List<Horse>(); }
[Display(Name = "Race Name"), Required]
public string RaceName { get; set; }
[Display(Name = "Horses In Race")]
public List<Horse> HorsesInRace { get; set; }
}
public class Horse
{
[Display(Name = "Horse's Name"), Required]
public string Name { get; set; }
[Display(Name = "Horse's Age"), Required]
public int Age { get; set; }
}
Index.cshtml
@model CollectionAjaxPosting.Models.Race
<h1>Race Details</h1>
@using (Html.BeginForm())
{
@Html.ValidationSummary()
<hr />
<div>
@Html.DisplayNameFor(x => x.RaceName)
@Html.EditorFor(x => x.RaceName)
@Html.ValidationMessageFor(x => x.RaceName)
</div>
<hr />
<div id="horse-listing">@Html.EditorForMany(x => x.HorsesInRace)</div>
<button id="btn-add-horse" type="button">Add New Horse</button>
<input type="submit" value="Enter Horses" />
}
<script type="text/javascript">
$(document).ready(function() {
//add button logic
$('#btn-add-horse').click(function() {
$.ajax({
url: '@Url.Action("AjaxMakeHorseEntry")',
cache: false,
method: 'GET',
success: function (html) {
$('#horse-listing').append(html);
}
})
});
//delete-horse buttons
$('#horse-listing').on('click', 'button.delete-horse', function() {
var horseEntryToRemove = $(this).closest('div.horse');
horseEntryToRemove.prev('input[type=hidden]').remove();
horseEntryToRemove.remove();
});
});
</script>
Visualizzazioni/condiviso/EditorTemplates/Horse.c shtml
@model CollectionAjaxPosting.Models.Horse
<div class="horse">
<div>
@Html.DisplayNameFor(x => x.Name)
@Html.EditorFor(x => x.Name)
@Html.ValidationMessageFor(x => x.Name)
</div>
<div>
@Html.DisplayNameFor(x => x.Age)
@Html.EditorFor(x => x.Age)
@Html.ValidationMessageFor(x => x.Age)
</div>
<button type="button" class="delete-horse">Remove Horse</button>
<hr />
</div>
Vista/Home/AjaxMakeHorseEntry.cshtml flusso
@model IEnumerable<CollectionAjaxPosting.Models.Horse>
@Html.EditorForMany(x => x, "HorsesInRace")
I dati funziona con questo codice. Una persona è in grado di creare ed eliminare voci di cavalli tanto quanto vogliono sulla pagina, e quando il modulo viene inviato, tutti i valori inseriti vengono assegnati al metodo di azione.
Tuttavia, se l'utente non entra nelle informazioni [Required]
su una voce cavallo, ModelState.IsValid
sarà falso che mostra ancora una volta la forma, ma non messaggi di convalida sarà mostrato per le proprietà del cavallo. L'errore di convalida viene visualizzato nell'elenco ValidationSummary
.
Ad esempio, se viene lasciato vuoto, insieme a uno Horse's Name
, verrà visualizzato un messaggio di convalida per il primo. Quest'ultimo avrà una validazione <span>
con la classe "validazione campo valida".
Sono molto sicuro che questo è causato perché il metodo EditorForMany
crea nuovi GUID per ogni proprietà ogni volta che viene creata la pagina, in modo messaggi di convalida non possono essere abbinate al campo corretto.
Cosa posso fare per risolvere questo problema? Devo abbandonare la creazione dell'indice guida o è possibile apportare una modifica al metodo EditorForMany
per consentire il corretto inoltro dei messaggi di convalida?