Я столкнулся с точно такой же проблемой сегодня. Как и вы, я не привязываю свой View напрямую к моей модели, но использую промежуточный класс ViewDataModel, который содержит экземпляр модели и любые параметры / конфигурации, которые я хотел бы отправить в представление.
Я закончил тем, что изменил BindProperty
в DataAnnotationsModelBinder, чтобы обойти NullReferenceException
, и мне лично не понравилось, что свойства были связаны только в том случае, если они действительны (см. Причины ниже).
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;
}
}
}
}
}
}
Я также изменил его, чтобы оно всегда связывало данные свойства независимо от того, действительно ли оно или нет. Таким образом, я могу просто передать модель обратно в представление без сброса недопустимых свойств.
Отрывок контроллера
[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)
}
Мой класс Model (Person) происходит из веб-службы, поэтому я не могу напрямую на них накладывать атрибуты. Я решил это следующим образом:
Пример с вложенными аннотациями данных
[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; }
}
Теперь, если я привяжу поле формы к [ViewDataModel.]Person.NearestGeographicRegion.Text
& [ViewDataModel.]Person.NearestGeographicRegion.Value
, ModelState начнет их корректную проверку, и DataAnnotationsModelBinder также правильно их связывает.
Этот ответ не окончательный, это результат того, что я почесал голову сегодня днем.
Он не был должным образом протестирован, несмотря на то, что он прошел модульные тесты в проекте , который начал Брайан Уилсон, и большую часть моих собственных ограниченных испытаний. Для истинного закрытия по этому вопросу я хотел бы услышать Брэд Уилсон мысли по этому решению.