8

我正在尝试让 ASP.NET MVC 3 从复杂的嵌套对象生成表单。我发现有一种验证行为是出乎意料的,我不确定这是否是 DefaultModelBinder 中的错误。

如果我有两个对象,让我们将“父”称为“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" });
        }
    }
}

这是内部对象:

    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”方法会被命中两次,但“OuterObject”中的“Validate”方法根本不会被命中。

如果你从“InnerObject”中取出 IValidatableObject,那么“OuterObject”上的那个就会被击中。

这是一个错误,还是我应该期望它以这种方式工作?如果我应该期望它,最好的解决方法是什么?

4

3 回答 3

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 基类,而不是像以前那样创建关系?(反之亦然)并将视图作为 ViewModel 提供基础对象?

这意味着当模型绑定时,OuterObject 的默认构造函数(或者你的基类)将被调用间接调用 Validate 在两个对象上。

即类:

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

看法:

@model MvcNestedObjectTest.Models.OuterObject

控制器动作:

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