Jak zmusić MVC do walidacji IValidatableObject
Wygląda na to, że gdy MVC waliduje Model, najpierw uruchamia atrybuty DataAnnotation (takie jak wymagane lub zakres), a jeśli któryś z nich się nie powiedzie, pomija uruchamianie metody Validate na moim modelu IValidatableObject.
Czy istnieje sposób, aby MVC uruchomił tę metodę, nawet jeśli druga Walidacja nie powiedzie się?
2 answers
Możesz ręcznie wywołać validate (), przekazując nową instancję ValidationContext, tak:
[HttpPost]
public ActionResult Create(Model model) {
if (!ModelState.IsValid) {
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
return View(post);
}
}
Zastrzeżeniem tego podejścia jest to, że w przypadkach, gdy nie ma błędów na poziomie właściwości (DataAnnotation), Walidacja zostanie uruchomiona dwukrotnie. Aby tego uniknąć, możesz dodać właściwość do modelu, powiedzmy zatwierdzoną logicznie, którą ustawisz na true w metodzie Validate() po uruchomieniu, a następnie sprawdzić przed ręcznym wywołaniem metody w kontrolerze.
Więc w Twoim Kontroler:
if (!ModelState.IsValid) {
if (!model.Validated) {
var validationResults = model.Validate(new ValidationContext(model, null, null));
foreach (var error in validationResults)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
}
return View(post);
}
I w twoim modelu:
public bool Validated { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// perform validation
Validated = true;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-29 14:01:52
Jest sposób, aby to zrobić bez konieczności kodowania boilerplate u góry każdej akcji kontrolera.
Będziesz musiał wymienić domyślny segregator modelu na jeden z twoich własnych:
protected void Application_Start()
{
// ...
ModelBinderProviders.BinderProviders.Clear();
ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
// ...
}
Twój model dostawcy segregatora wygląda tak:
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
return new CustomModelBinder();
}
}
Teraz Utwórz niestandardowy segregator modelu, który wymusza walidację. W tym miejscu odbywa się podnoszenie ciężarów:
public class CustomModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
ForceModelValidation(bindingContext);
}
private static void ForceModelValidation(ModelBindingContext bindingContext)
{
var model = bindingContext.Model as IValidatableObject;
if (model == null) return;
var modelState = bindingContext.ModelState;
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
{
foreach (var memberName in error.MemberNames)
{
// Only add errors that haven't already been added.
// (This can happen if the model's Validate(...) method is called more than once, which will happen when
// there are no property-level validation failures.)
var memberNameClone = memberName;
var idx = modelState.Keys.IndexOf(k => k == memberNameClone);
if (idx < 0) continue;
if (modelState.Values.ToArray()[idx].Errors.Any()) continue;
modelState.AddModelError(memberName, error.ErrorMessage);
}
}
}
}
Będziesz potrzebował również metody rozszerzenia IndexOf. Jest to tania implementacja, ale zadziała:
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) throw new ArgumentNullException("source");
if (predicate == null) throw new ArgumentNullException("predicate");
var i = 0;
foreach (var item in source)
{
if (predicate(item)) return i;
i++;
}
return -1;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-23 03:39:47