7

Próbuję użyć środowiska ASP.NET MVC 3 do generowania formularzy ze złożonych, zagnieżdżonych obiektów. Istnieje jedno zachowanie sprawdzania poprawności, które znalazłem nieoczekiwane i nie jestem pewien, czy jest to błąd w DefaultModelBinder czy nie.Sprawdzanie poprawności programu ASP.NET MVC 3 na obiektach zagnieżdżonych nie działa zgodnie z oczekiwaniami - sprawdza dwa razy obiekt podrzędny, a nie obiekt nadrzędny.

Jeśli mam dwa obiekty, pozwala wywołać "rodzic" jeden "OuterObject", a to ma właściwość typu "InnerObject" (dziecko):

public class OuterObject : IValidatableObject 
{ 
    [Required] 
    public string OuterObjectName { get; set; } 

    public InnerObject FirstInnerObject { get; set; } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     if (!string.IsNullOrWhiteSpace(OuterObjectName) && string.Equals(OuterObjectName, "test", StringComparison.CurrentCultureIgnoreCase)) 
     { 
      yield return new ValidationResult("OuterObjectName must not be 'test'", new[] { "OuterObjectName" }); 
     } 
    } 
} 

Oto InnerObject:

public class InnerObject : IValidatableObject 
{ 
    [Required] 
    public string InnerObjectName { get; set; } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     if (!string.IsNullOrWhiteSpace(InnerObjectName) && string.Equals(InnerObjectName, "test", StringComparison.CurrentCultureIgnoreCase)) 
     { 
      yield return new ValidationResult("InnerObjectName must not be 'test'", new[] { "InnerObjectName" }); 
     } 
    } 
} 

Zauważysz walidację, którą umieściłem na obu .. tylko niektóre sprawdzanie atrapa, aby powiedzieć, że pewna wartość nie może być równa "test".

Oto widok, który będzie wyświetlany w (Index.cshtml):

@model MvcNestedObjectTest.Models.OuterObject 
@{ 
    ViewBag.Title = "Home Page"; 
} 

@using (Html.BeginForm()) { 
<div> 
    <fieldset> 
     <legend>Using "For" Lambda</legend> 

     <div class="editor-label"> 
      @Html.LabelFor(m => m.OuterObjectName) 
     </div> 
     <div class="editor-field"> 
      @Html.TextBoxFor(m => m.OuterObjectName) 
      @Html.ValidationMessageFor(m => m.OuterObjectName) 
     </div> 

     <div class="editor-label"> 
      @Html.LabelFor(m => m.FirstInnerObject.InnerObjectName) 
     </div> 
     <div class="editor-field"> 
      @Html.TextBoxFor(m => m.FirstInnerObject.InnerObjectName) 
      @Html.ValidationMessageFor(m => m.FirstInnerObject.InnerObjectName) 
     </div> 

     <p> 
      <input type="submit" value="Test Submit" /> 
     </p> 
    </fieldset> 
</div> 
} 

..i wreszcie jest tu HomeController:

public class HomeController : Controller 
{ 
    public ActionResult Index() 
    { 
     var model = new OuterObject(); 
     model.FirstInnerObject = new InnerObject(); 
     return View(model); 
    } 

    [HttpPost] 
    public ActionResult Index(OuterObject model) 
    { 
     if (ModelState.IsValid) 
     { 
      return RedirectToAction("Index"); 
     } 
     return View(model); 
    } 
} 

Co znajdziesz to, że gdy model zostanie sprawdzony przez DefaultModelBinder, metoda "Validate" w "InnerObject" zostanie dwukrotnie trafiona, ale metoda "Validate" w "OuterObject" nie zostanie w ogóle trafiona.

Jeśli zdejmiesz obiekt IValidatableObject z "InnerObject", zostanie pobity ten z "OuterObject".

Czy to błąd, czy też powinienem oczekiwać, że zadziała w ten sposób? Jeśli powinienem się tego spodziewać, jaki jest najlepszy sposób obejścia tego problemu?

Odpowiedz

0

Czy stworzyłeś klasę bazową OuterObject dla InnerObject, zamiast tworzyć relację tak jak Ty? (Lub odwrotnie) i zapewnić widok obiektu podstawowego jako ViewModel?

Będzie to oznaczać, że gdy powiązanie modelu z domyślnym konstruktorem obiektu OuterObject (lub jakiejkolwiek klasy jest twoją bazą), zostanie wywołane pośrednio wywołanie sprawdzania poprawności w obu obiektach.

tj Klasa:

public class OuterObject : InnerObject, IValidateableObject 
{ 
... 
} 

Widok:

@model MvcNestedObjectTest.Models.OuterObject 

Controller Działanie:

public ActionResult Index(OuterObject model) 
+0

Dzięki myślałem o tym i będzie pracować dla tej konkretnej sytuacji, ale jak teh przedmiot staje się bardziej skomplikowana, że ​​nie będzie działać. Na przykład, jeśli potrzebuję InnerObject1, SomeString, InnerObject2, SomeOtherString (np. Zagnieżdżone obiekty między innymi właściwościami). – nootn

+0

@nootn Czy próbowałeś [Fluent Validation] (http: // http: //fluentvalidation.codeplex.com/wikipage? title = mvc & referingTitle = Documentation), który jest zaawansowanym sposobem sprawdzania poprawności zależności i zagnieżdżonych reguł sprawdzania poprawności? – amythn04

+0

Zajrzałem do tego i wygląda ładnie, ale w naszej organizacji zdecydowaliśmy się zastosować wbudowane adnotacje danych jako standard do sprawdzania poprawności modeli w MVC. Sądzę, że moglibyśmy użyć kombinacji, ale wciąż nie ominęło tego, że nie zachowuje się tak, jak się spodziewano, to trochę pułapka. – nootn

1

Ta odpowiedź jest tylko, aby zapewnić jedno obejście właśnie pomyślał - tak tak naprawdę nie jest odpowiedzią! Nadal nie jestem pewien, czy jest to błąd lub jaki jest najlepszy sposób obejścia tego problemu, ale tutaj jest jedna opcja.

Usunięcie niestandardowej logiki walidacji z "InnerObject" i włączenie jej do "OuterObject" wydaje się działać dobrze. Zasadniczo działa to wokół błędu, dopuszczając tylko najwyższy obiekt do dowolnej niestandardowej weryfikacji.

Oto nowy InnerObject:

//NOTE: have taken IValidatableObject off as this causes the issue - we must remember to validate it manually in the "Parent"! 
public class InnerObject //: IValidatableObject 
{ 
    [Required] 
    public string InnerObjectName { get; set; } 
} 

A oto nowy OuterObject (z kodem Validation skradzione z InnerObject):

public class OuterObject : IValidatableObject 
{ 
    [Required] 
    public string OuterObjectName { get; set; } 

    public InnerObject FirstInnerObject { get; set; } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     if (!string.IsNullOrWhiteSpace(OuterObjectName) && string.Equals(OuterObjectName, "test", StringComparison.CurrentCultureIgnoreCase)) 
     { 
      yield return new ValidationResult("OuterObjectName must not be 'test'", new[] { "OuterObjectName" }); 
     } 

     if (FirstInnerObject != null) 
     { 
      if (!string.IsNullOrWhiteSpace(FirstInnerObject.InnerObjectName) && 
       string.Equals(FirstInnerObject.InnerObjectName, "test", StringComparison.CurrentCultureIgnoreCase)) 
      { 
       yield return new ValidationResult("InnerObjectName must not be 'test'", new[] { "FirstInnerObject.InnerObjectName" }); 
      } 
     } 
    } 
} 

Działa to jak bym się spodziewał, zaczepiając w górę błąd sprawdzania poprawności dla każdego pola poprawnie.

To nie jest świetne rozwiązanie, ponieważ jeśli potrzebuję zagnieździć "InnerObject" w innej klasie, to nie udostępnia tej walidacji - muszę ją powielić. Oczywiście mogłem mieć metodę do przechowywania logiki, ale każda klasa "rodzica" musi pamiętać, aby "sprawdzić" klasę podrzędną.

1

nie jestem pewien, że to jest problem z MVC 4 więcej, ale ...

Jeśli używasz częściowy widok dokonane tylko dla InnerObjects, będą one potwierdzić prawidłowo.

<fieldset> 
    <legend>Using "For" Lambda</legend> 

    <div class="editor-label"> 
     @Html.LabelFor(m => m.OuterObjectName) 
    </div> 
    <div class="editor-field"> 
     @Html.TextBoxFor(m => m.OuterObjectName) 
     @Html.ValidationMessageFor(m => m.OuterObjectName) 
    </div> 

    @Html.Partial("_InnerObject", Model.InnerObject) 

    <p> 
     <input type="submit" value="Test Submit" /> 
    </p> 
</fieldset> 

Następnie dodać tę częściową „_InnerObject.cshtml”:

@model InnerObject 

    <div class="editor-label"> 
     @Html.LabelFor(m => m.InnerObjectName) 
    </div> 
    <div class="editor-field"> 
     @Html.TextBoxFor(m => m.InnerObjectName) 
     @Html.ValidationMessageFor(m => m.InnerObjectName) 
    </div> 
Powiązane problemy