8

ASP.NET MVC 3を取得して、複雑なネストされたオブジェクトからフォームを生成しようとしています。予期しない検証動作が1つ見つかりましたが、それがDefaultModelBinderのバグであるかどうかはわかりません。

2つのオブジェクトがある場合、「親」を「OuterObject」と呼び、タイプが「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" });
        }
    }
}

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" });
        }
    }
}

あなたは私が両方に行った検証に気付くでしょう..いくつかの値が「テスト」に等しくないことを言うためのいくつかのダミー検証。

これが(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>
}

..そして最後にここに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);
    }
}

モデルがDefaultModelBinderによって検証されると、「InnerObject」の「Validate」メソッドは2回ヒットしますが、「OuterObject」の「Validate」メソッドはまったくヒットしません。

「InnerObject」からIValidatableObjectを削除すると、「OuterObject」のIValidatableObjectがヒットします。

これはバグですか、それともそのように機能することを期待する必要がありますか?私がそれを期待する必要がある場合、最良の回避策は何ですか?

4

3 に答える 3

1

この回答は、私が考えた 1 つの回避策を提供するためのものです。したがって、実際には回答ではありません。これがバグなのか、最善の回避策なのかはまだわかりませんが、ここに 1 つのオプションがあります。

「InnerObject」からカスタム検証ロジックを削除して「OuterObject」に組み込むと、問題なく動作するようです。したがって、基本的にこれは、最上位のオブジェクトにのみカスタム検証を許可することでバグを回避します。

新しい 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; }
}

そして、これが新しい OuterObject (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" });
            }
        }
    }
}

これは期待どおりに機能し、検証エラーを各フィールドに正しく接続します。

「InnerObject」を他のクラスにネストする必要がある場合、その検証を共有しないため、それを複製する必要があるため、これは優れたソリューションではありません。明らかに、ロジックを格納するためのメソッドをクラスに持つことができますが、各「親」クラスは子クラスを「検証」することを忘れないでください。

于 2012-03-20T21:03:06.037 に答える
1

これが MVC 4 の問題かどうかはわかりませんが、...

InnerObjects 用に作成された部分ビューを使用すると、正しく検証されます。

<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>

次に、この部分的な "_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>
于 2013-02-01T21:01:54.933 に答える
0

リレーションシップを作成する代わりに、InnerObject の OuterObject 基本クラスを作成する必要がありましたか? (またはその逆)ビューをビューモデルとしてベースオブジェクトに提供しますか?

これは、モデル バインド時に、OuterObject (またはベースとなるクラス) の既定のコンストラクターが間接的に呼び出され、両方のオブジェクトで Validate が呼び出されることを意味します。

すなわちクラス:

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

意見:

@model MvcNestedObjectTest.Models.OuterObject

コントローラーのアクション:

public ActionResult Index(OuterObject model)
于 2012-03-20T08:06:24.413 に答える