Me enfrenté exactamente al mismo problema hoy. Al igual que usted, no ato mi Vista directamente a mi Modelo, sino que utilizo una clase intermedia ViewDataModel que contiene una instancia del Modelo y cualquier parámetro/configuración que me gustaría enviar a la vista.
Terminé modificando BindProperty
en el DataAnnotationsModelBinder para eludir el NullReferenceException
, y personalmente no me gusta que las propiedades solo estén vinculadas si fueran válidas (vea los motivos a continuación).
protected override void BindProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor) {
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
// Only bind properties that are part of the request
if (bindingContext.ValueProvider.DoesAnyKeyHavePrefix(fullPropertyKey)) {
var innerContext = new ModelBindingContext() {
Model = propertyDescriptor.GetValue(bindingContext.Model),
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ModelType = propertyDescriptor.PropertyType,
ValueProvider = bindingContext.ValueProvider
};
IModelBinder binder = Binders.GetBinder(propertyDescriptor.PropertyType);
object newPropertyValue = ConvertValue(propertyDescriptor, binder.BindModel(controllerContext, innerContext));
ModelState modelState = bindingContext.ModelState[fullPropertyKey];
if (modelState == null)
{
var keys = bindingContext.ValueProvider.FindKeysWithPrefix(fullPropertyKey);
if (keys != null && keys.Count() > 0)
modelState = bindingContext.ModelState[keys.First().Key];
}
// Only validate and bind if the property itself has no errors
//if (modelState.Errors.Count == 0) {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
//}
// There was an error getting the value from the binder, which was probably a format
// exception (meaning, the data wasn't appropriate for the field)
if (modelState.Errors.Count != 0) {
foreach (var error in modelState.Errors.Where(err => err.ErrorMessage == "" && err.Exception != null).ToList()) {
for (var exception = error.Exception; exception != null; exception = exception.InnerException) {
if (exception is FormatException) {
string displayName = GetDisplayName(propertyDescriptor);
string errorMessage = InvalidValueFormatter(propertyDescriptor, modelState.Value.AttemptedValue, displayName);
modelState.Errors.Remove(error);
modelState.Errors.Add(errorMessage);
break;
}
}
}
}
}
}
También he modificado para que siempre une los datos sobre la propiedad no importa si es válido o no. De esta forma, puedo pasar el modelo a la vista sin que se restablezcan las propiedades inválidas a nulo.
controlador Extracto
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(ProfileViewDataModel model)
{
FormCollection form = new FormCollection(this.Request.Form);
wsPerson service = new wsPerson();
Person newPerson = service.Select(1, -1);
if (ModelState.IsValid && TryUpdateModel<IPersonBindable>(newPerson, "Person", form.ToValueProvider()))
{
//call wsPerson.save(newPerson);
}
return View(model); //model.Person is always bound no null properties (unless they were null to begin with)
}
Mi clase Modelo (persona) viene de un servicio web así que no puedo poner atributos en ellos directamente, la forma en que esto se resolvió de la siguiente manera:
Ejemplo con DataAnnotations anidados
[Validation.MetadataType(typeof(PersonValidation))]
public partial class Person : IPersonBindable { } //force partial.
public class PersonValidation
{
[Validation.Immutable]
public int Id { get; set; }
[Validation.Required]
public string FirstName { get; set; }
[Validation.StringLength(35)]
[Validation.Required]
public string LastName { get; set; }
CategoryItemNullable NearestGeographicRegion { get; set; }
}
[Validation.MetadataType(typeof(CategoryItemNullableValidation))]
public partial class CategoryItemNullable { }
public class CategoryItemNullableValidation
{
[Validation.Required]
public string Text { get; set; }
[Validation.Range(1,10)]
public string Value { get; set; }
}
Ahora si se unen af orm campo a [ViewDataModel.]Person.NearestGeographicRegion.Text
& [ViewDataModel.]Person.NearestGeographicRegion.Value
el modelo Estado comienza a validarlos correctamente y DataAnnotationsModelBinder también los vincula correctamente.
Esta respuesta no es definitiva, es el producto de rascarme la cabeza esta tarde. No se ha probado correctamente, aunque pasó las pruebas unitarias en the project Brian Wilson comenzó y la mayoría de mis propias pruebas limitadas. Para un cierre real en este asunto, me gustaría escuchar Brad Wilson ideas sobre esta solución.
tengo un modelo similar pero generalmente edito objetos individuales a la vez. por ejemplo: tengo un objeto de anuncio que puede o no tener una serie de archivos adjuntos (imágenes, archivos PDF), pero edito el anuncio por sí mismo y no obligo al validador a descender a los objetos secundarios del anuncio. Luego editaré los objetos secundarios por separado - misma vista, pero diferente acción POST. Ahora estoy interesado. ¿Cómo funcionan tus acciones para validar enormes árboles de objetos? y ¿cómo funciona la interfaz de usuario para eso? –
En cuanto a las vistas: He descrito cómo hacerlo en http://devermind.com/linq/aspnet-mvc-using-custom-viewmodels-with-post-action-methods En cuanto a la validación: Hasta ahora he estado utilizando la validación del lado del servidor de la misma manera que se describe en el tutorial de Scott Gu: http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx. Mi controlador luego recoge los errores de validación de diferentes entidades como esta: ModelState.AddRuleViolations (model.User.GetRuleViolations(), "User"); ModelState.AddRuleViolations (model.Company.GetRuleViolations(), "Company"); –
Estoy seguro de que es un error en algún lugar de mi código de modelo de encuadernación, pero no he tenido tiempo de rastrearlo. Los entregables más urgentes han estado en el camino. :( –