mvc3 ValidationSummary 排除属性错误 IValidatableObject

发布于 2024-11-16 16:23:03 字数 345 浏览 5 评论 0 原文

我的模型( A 类)有一个 B 类型的属性(称为 b ),并实现了 IValidatableObject

View 在验证摘要中有 @Html.ValidationSummary(true)

我想排除与属性相关的错误。 在 B 类 IValidatableObject 实现中返回没有 memberNames 的 ValidationResult

但来自 IValidatableObject 的 B 类验证错误不会显示,因为 B 类是类的属性A

如何显示B类非属性验证错误?

My model ( class A ) has a property ( called b ) of type B with IValidatableObject implemented.

View has got @Html.ValidationSummary(true)

In the validation summary I want to exclude errors related to properties.
In class B IValidatableObject implementation is returning ValidationResult with no memberNames

But class B valiadtion errors from IValidatableObject are not displayed since class B is a property on class A

How to display class B non-property validation errors?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

云醉月微眠 2024-11-23 16:23:03

我认为这非常简单,让我用一个例子来解释一下。
首先让我创建您面临的问题,然后我将解释如何解决。

1)声明我的模型。

public class ClassA
{
    [Required]
    public string Name { get; set; }
    public ClassB CBProp { get; set; }
}

public class ClassB:IValidatableObject
{
    [Required]
    public string MyProperty { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
            yield return new ValidationResult("MaxLength reached");
    }
}

2) 声明简单的动作。

public class HomeController : Controller
{       
    [HttpGet]
    public ActionResult Test()
    {
        ClassA ca = new ClassA();
        return View(ca);
    }

    [HttpPost]
    public ActionResult Test(ClassA ca)
    {            
        return View(ca);
    }
}

3) 让我为 ClassB 创建一个简单的视图和编辑器模板。

测试视图:

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>ClassA</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
            @Html.EditorFor(m => m.CBProp, "TestB")    
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

EditorTemplate

<div class="editor-label">
        @Html.LabelFor(model => model.MyProperty)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.MyProperty)
        @Html.ValidationMessageFor(model => model.MyProperty)
    </div>

4) 现在视图将如下所示:

在此处输入图像描述

5) 现在,如果我们单击“提交” 。无需输入任何数据。它将按预期显示属性验证错误。

在此处输入图像描述

6) 现在,如果我们输入一个长度 > 的字符串10 在 MyProperty 中,应该显示错误消息“MaxLength returned”,但不会显示错误:) :)

在此处输入图像描述

原因

因此,如果我们查看视图的代码,我们可以找到这一行

 @Html.ValidationSummary(true)  /// true, will excludePropertyErrors

:Since, CBProp is a property in ClassA, ValidationSummary(true) 将排除 CBProp 中的任何错误。所以你不会发现显示错误消息。然而,有多种选择可以实现这一点。

选项

1) 设置@Html.ValidationSummary()

这将显示错误消息,但它也会显示任何其他错误消息(这是多余的),例如,

在此处输入图像描述

2) 在编辑器模板中设置@Html.ValidationSummary(true)。但它会显示错误消息,例如

在此处输入图像描述

3) 在 ClassB 的 Validate 方法中,指定属性名称以及ValidationResult 中的错误消息。现在它将被视为属性的验证错误,并将由编辑器模板中的 @Html.ValidationMessageFor(model => model.MyProperty) 显示。

代码

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
            yield return new ValidationResult("MaxLength reached", new List<string> { "MyProperty" });
    }

错误看起来像

在此处输入图像描述

I我认为现在已经清楚为什么没有显示错误消息,以及可用的选项是什么。由您决定最适合您的方法。

干杯

I think this is pretty much straight forward, Let me explain with an example.
First let me create the problem you are facing, then i will explain how to solve.

1) Declare My models.

public class ClassA
{
    [Required]
    public string Name { get; set; }
    public ClassB CBProp { get; set; }
}

public class ClassB:IValidatableObject
{
    [Required]
    public string MyProperty { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
            yield return new ValidationResult("MaxLength reached");
    }
}

2) Declare simple actions.

public class HomeController : Controller
{       
    [HttpGet]
    public ActionResult Test()
    {
        ClassA ca = new ClassA();
        return View(ca);
    }

    [HttpPost]
    public ActionResult Test(ClassA ca)
    {            
        return View(ca);
    }
}

3) Let me create a simple view and an editor template for ClassB.

Test View:

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>ClassA</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
            @Html.EditorFor(m => m.CBProp, "TestB")    
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

EditorTemplate

<div class="editor-label">
        @Html.LabelFor(model => model.MyProperty)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.MyProperty)
        @Html.ValidationMessageFor(model => model.MyProperty)
    </div>

4) Now the view will look like,

enter image description here

5) Now if we will click on Submit. with out entering any data. It will show property validation error as expected.

enter image description here

6) Now if we will enter a string with length > 10 in MyProperty, it should show the error message "MaxLength reached", But the error will not be displayed :) :)

enter image description here

Reason for this

So, If we see the code for the view, we can find the line

 @Html.ValidationSummary(true)  /// true, will excludePropertyErrors

Since, CBProp is a property in ClassA, ValidationSummary(true) will exclude any error in CBProp. So you will not find the error message being displayed. How ever there are several options available for this.

Options

1) Set @Html.ValidationSummary()

This will display the error message, But it will also display any other error message(which is redundant),like,

enter image description here

2) Set @Html.ValidationSummary(true) in the Editor Template. But it will show the error message like,

enter image description here

3) In Validate method of ClassB, specify property name along with error message in ValidationResult.Now it will be treated as a validation error for the property and will be displayed by @Html.ValidationMessageFor(model => model.MyProperty) in Editor Template.

Code

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
            yield return new ValidationResult("MaxLength reached", new List<string> { "MyProperty" });
    }

Error looks like

enter image description here

I think its now clear why the error message was not displayed, and what are the options available. Its up to you to decide the approach best suitable for you.

Cheers

零時差 2024-11-23 16:23:03

虽然验证摘要的这种行为可能不适合您的情况,但通常必须将其视为“正确”。当模型中包含的子对象中创建错误时,会将正确的前缀添加到错误中。也就是说,如果子对象包含在属性 MyProp 中,则前缀 MyProp 会自动添加到所有错误中。这对于为创建的所有错误提供正确的名称是必要的 - 否则 Validationsummary 和 ValidationMessageFor 都无法正常工作 - 因为它们引用全名(包括整个前缀)。这是避免歧义的唯一方法,因为两个不同的子对象中可能有两个 Name 属性。

然而,通常,当子对象中生成的错误不是简单的属性级别错误而是“整个对象”级别错误时,这种“正确”行为是不合适的。在这种情况下,您可能希望它们出现在一般验证摘要中。

您可以通过两种方式面对这个问题:

  1. 使用另一个特定于子对象的验证摘要
  2. 错误冒泡 - 我经常使用错误冒泡来表示模型的子部分(未显示在屏幕上)包含错误,因此用户可以打开一个详细信息窗口(jQuery 对话框或类似)来查看它们。基本上,错误冒泡包括使用 foreach 处理 ModelState 中的所有错误,然后提升其中的一些错误。提升意味着删除错误前缀的最后部分。当升级错误时,您可以保留或不保留原始错误 - 保留原始错误也更容易,并且在大多数情况下这是正确的做法。请注意,您无法在循环遍历所有条目时删除条目 - 您必须将其放入列表中,然后在循环终止后将其删除。

促销标准可能取决于您的需求。我举个例子:

  • 提升属性级别错误会将其转换为对象级别错误。
  • 提升子对象级别错误会将其转换为外部对象的对象级别错误。您应该对这种情况感兴趣 - 只需提升与包含整个对象而不是简单值的根 ViewModel 的属性相关的对象级错误!

错误处理可以在您可以定义的自定义 ActionFilter 中执行,并与多个操作方法一起重复使用。

下面是一个简单的PromoteAttribute ActionFilter 的代码。它的用途是:

[Promote("prop1.SubProp1 Prop2")]
public ActionResult MyMethod( ...

也就是说,你向它传递一个你想要提升到 Root 模型的表达式错误列表,如果它在 ModelState 中发现与它们匹配的错误,它就会提升它们 - 显然这只是一个简单的例子 - 你可以只提升一个升级而不是根,你可以使用复杂的标准来定位错误以升级而不是列出它们:

public class PromoteAttribute : ActionFilterAttribute
{
    string[] expressions;
    public PromoteAttribute(string toPromote)
    {
        expressions = toPromote.Split(' ');
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ModelStateDictionary modelState=filterContext.Controller.ViewData.ModelState;
        foreach(var x in expressions)
        {
            if (modelState.ContainsKey(x))
            {
                var entry = modelState[x];
                if (entry.Errors.Count == 0) continue; 

                foreach (var error in entry.Errors) modelState.AddModelError("", error.ErrorMessage);

            }
        }
    }
}

While this behaviour of the validation summary may be inappropriate in your case, it has to be considered "correct" in general. When errors are created in sub-objects contained in the model, the right prefix is added to the errors. That is, if a sub-object is contained in a property MyProp then the prefix MyProp is automatically added to all errors. This is necessary to give the right name to all errors that are created - without this neither the Validationsummary nor the ValidationMessageFor would work properly - because they refer to the full names (the one including the whole prefixes). This is the only way to avoid ambiguities, because you might have two Name properties in two different sub-objects.

However, often, this "correct" behaviour is inappropriate when the errors generated in the sub-object are not simple properties level errors but "whole object" level errors. In such cases you might wish they appear in the general validation summary.

You can face this problems in two ways:

  1. Using another validation summary specific for the sub-object
  2. Error Bubbling - I often use errors bubbling to signal that a sub-parts of the model - not shown on the screen - contains errors, so the user can open a detail window (jQuery Dialog or similar) to see them. Basically, error bubbling consists in processing all errors in the ModelState with a foreach, and then promoting some of them up. Promoting up means deleting the last part of the error prefix. When promoting an error you may keep the original error or not - keeping the original error too is easier and in most of the cases it is the correct thing to do. Note that you can't remove an entry while looping through all entries - you have to put it in a list and then remove it after the loop is terminated.

The promoting criteria may depend on your needs. I give you some example:

  • Promoting up a property level error transforms it into an object level error.
  • Promoting a sub-object level error transforms it into an object level error of the outer object. This is the case that should interest you - just promote the object level errors associated to properties of the root ViewModel that contains whole objects instead of simple values!

The error processing can be performed within a custom ActionFilter that you may define, and re-use with several action methods.

Below is the code for a simple PromoteAttribute ActionFilter. Its use is:

[Promote("prop1.SubProp1 Prop2")]
public ActionResult MyMethod( ...

That is you pass it a list of expression errors you would like to promote to the Root model, and if it find errors matching them in ModelState it promotes them - obviously it is just a simple example - you can promote just one level up instead of the root, and you can use a complex criterion to locate the errors to promote instead of listing them:

public class PromoteAttribute : ActionFilterAttribute
{
    string[] expressions;
    public PromoteAttribute(string toPromote)
    {
        expressions = toPromote.Split(' ');
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ModelStateDictionary modelState=filterContext.Controller.ViewData.ModelState;
        foreach(var x in expressions)
        {
            if (modelState.ContainsKey(x))
            {
                var entry = modelState[x];
                if (entry.Errors.Count == 0) continue; 

                foreach (var error in entry.Errors) modelState.AddModelError("", error.ErrorMessage);

            }
        }
    }
}
柠檬色的秋千 2024-11-23 16:23:03

找出 MVC3 的源代码并进行编辑以允许包含属性。

@Html.ValidationSummary(new [] { "PropertyName" })

将包括名为 PropertyName 的属性

@Html.ValidationSummary(new [] { "ArrayName[]" })

将包括属性 ArrayName[0]、ArrayName[1] 等

@Html.ValidationSummary(new [] { "ArrayName[]", "PropertyName" })

将包括两者。

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors)
{
    return ValidationSummary(htmlHelper, includePropertyErrors, null, null);
}

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message)
{
    return ValidationSummary(htmlHelper, includePropertyErrors, message, null);
}

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
{
    if (htmlHelper == null)
    {
        throw new ArgumentNullException("htmlHelper");
    }

    FormContext formContext = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;
    if (htmlHelper.ViewData.ModelState.IsValid)
    {
        if (formContext == null)
        {  // No client side validation
            return null;
        }

        // TODO: This isn't really about unobtrusive; can we fix up non-unobtrusive to get rid of this, too?
        if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {  // No client-side updates
            return null;
        }
    }

    string messageSpan;
    if (!string.IsNullOrEmpty(message))
    {
        TagBuilder spanTag = new TagBuilder("span");
        spanTag.SetInnerText(message);
        messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
    }
    else
    {
        messageSpan = null;
    }

    StringBuilder htmlSummary = new StringBuilder();
    TagBuilder unorderedList = new TagBuilder("ul");

    IEnumerable<ModelState> modelStates = from ms in htmlHelper.ViewData.ModelState
                                            where ms.Key == htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix ||
                                                includePropertyErrors.Any(property =>
                                                {
                                                    string prefixedProperty = string.IsNullOrEmpty(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix) ? property : htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix + "." + property;
                                                    if (property.EndsWith("[]"))
                                                    {
                                                        return prefixedProperty.Substring(0, property.Length - 2) == Regex.Replace(ms.Key, @"\[[^\]]+\]", string.Empty);
                                                    }
                                                    else
                                                    {
                                                        return property == ms.Key;
                                                    }
                                                })
                                            select ms.Value;

    if (modelStates != null)
    {
        foreach (ModelState modelState in modelStates)
        {
            foreach (ModelError modelError in modelState.Errors)
            {
                string errorText = GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError);
                if (!String.IsNullOrEmpty(errorText))
                {
                    TagBuilder listItem = new TagBuilder("li");
                    listItem.SetInnerText(errorText);
                    htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
                }
            }
        }
    }

    if (htmlSummary.Length == 0)
    {
        htmlSummary.AppendLine(@"<li style=""display:none""></li>");
    }

    unorderedList.InnerHtml = htmlSummary.ToString();

    TagBuilder divBuilder = new TagBuilder("div");
    divBuilder.MergeAttributes(htmlAttributes);
    divBuilder.AddCssClass((htmlHelper.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
    divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);

    if (formContext != null)
    {
        if (!htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {
            // client val summaries need an ID
            divBuilder.GenerateId("validationSummary");
            formContext.ValidationSummaryId = divBuilder.Attributes["id"];
            formContext.ReplaceValidationSummary = false;
        }
    }

    return new MvcHtmlString(divBuilder.ToString(TagRenderMode.Normal));
}

private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error)
{
    return string.IsNullOrEmpty(error.ErrorMessage) ? null : error.ErrorMessage;
}

Dug out MVC3's source code and edited to allow inclusion of properties.

@Html.ValidationSummary(new [] { "PropertyName" })

would include the property named PropertyName

@Html.ValidationSummary(new [] { "ArrayName[]" })

would include the properties ArrayName[0], ArrayName[1] etc.

@Html.ValidationSummary(new [] { "ArrayName[]", "PropertyName" })

would include both.

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors)
{
    return ValidationSummary(htmlHelper, includePropertyErrors, null, null);
}

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message)
{
    return ValidationSummary(htmlHelper, includePropertyErrors, message, null);
}

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
{
    if (htmlHelper == null)
    {
        throw new ArgumentNullException("htmlHelper");
    }

    FormContext formContext = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;
    if (htmlHelper.ViewData.ModelState.IsValid)
    {
        if (formContext == null)
        {  // No client side validation
            return null;
        }

        // TODO: This isn't really about unobtrusive; can we fix up non-unobtrusive to get rid of this, too?
        if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {  // No client-side updates
            return null;
        }
    }

    string messageSpan;
    if (!string.IsNullOrEmpty(message))
    {
        TagBuilder spanTag = new TagBuilder("span");
        spanTag.SetInnerText(message);
        messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
    }
    else
    {
        messageSpan = null;
    }

    StringBuilder htmlSummary = new StringBuilder();
    TagBuilder unorderedList = new TagBuilder("ul");

    IEnumerable<ModelState> modelStates = from ms in htmlHelper.ViewData.ModelState
                                            where ms.Key == htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix ||
                                                includePropertyErrors.Any(property =>
                                                {
                                                    string prefixedProperty = string.IsNullOrEmpty(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix) ? property : htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix + "." + property;
                                                    if (property.EndsWith("[]"))
                                                    {
                                                        return prefixedProperty.Substring(0, property.Length - 2) == Regex.Replace(ms.Key, @"\[[^\]]+\]", string.Empty);
                                                    }
                                                    else
                                                    {
                                                        return property == ms.Key;
                                                    }
                                                })
                                            select ms.Value;

    if (modelStates != null)
    {
        foreach (ModelState modelState in modelStates)
        {
            foreach (ModelError modelError in modelState.Errors)
            {
                string errorText = GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError);
                if (!String.IsNullOrEmpty(errorText))
                {
                    TagBuilder listItem = new TagBuilder("li");
                    listItem.SetInnerText(errorText);
                    htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
                }
            }
        }
    }

    if (htmlSummary.Length == 0)
    {
        htmlSummary.AppendLine(@"<li style=""display:none""></li>");
    }

    unorderedList.InnerHtml = htmlSummary.ToString();

    TagBuilder divBuilder = new TagBuilder("div");
    divBuilder.MergeAttributes(htmlAttributes);
    divBuilder.AddCssClass((htmlHelper.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
    divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);

    if (formContext != null)
    {
        if (!htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {
            // client val summaries need an ID
            divBuilder.GenerateId("validationSummary");
            formContext.ValidationSummaryId = divBuilder.Attributes["id"];
            formContext.ReplaceValidationSummary = false;
        }
    }

    return new MvcHtmlString(divBuilder.ToString(TagRenderMode.Normal));
}

private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error)
{
    return string.IsNullOrEmpty(error.ErrorMessage) ? null : error.ErrorMessage;
}
毁梦 2024-11-23 16:23:03

让我们从我们所知道的开始:

正如描述所示,如果我们有模型:

模型 A:

public class A
{
    public B ModelB { get; set; }
}

模型 B:

public class B : IValidatableObject
{
    public string Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> errors = new List<ValidationResult>();

        if (string.IsNullOrEmpty(Name)) {
            errors.Add(new ValidationResult("Please enter your name"));
        }

        return errors;
    }
}

我们的观点:

@model A

@Html.ValidationSummary(true)

@using (Html.BeginForm())
{
    @Html.EditorFor(model => model.ModelB.Name)

    <input type="submit" value="submit" />
}

那么编辑器将输出以下行:

<input class="text-box single-line" id="ModelB_Name" name="ModelB.Name" type="text" value="" />

如果我们将 post action 定义为:

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

那么当绑定到 A 模型时,DefaultModelBinder 将查找它将找到名为 ModelB.Name 的属性并绑定成功。

但是,DefaultModelBinder 针对 A 模型执行的模型验证将调用为模型 B 定义的验证。此验证将返回一条错误消息,该消息不是针对属性定义的,但因为此验证是复杂模型的一部分,所以它会使用“ModelB”键添加到 ModelState 中。

当调用 ValidationSummary 时,它会查找空白键(即为模型而不是属性定义的键)。由于不存在空白键,因此不会显示错误。

作为一个简单的解决方法,您可以为 B 模型定义一个 EditorTemplate

在包含视图的文件夹中,定义一个名为 EditorTemplates 的文件夹,并在其中创建一个与模型同名的视图(例如,在我的例子中为 B.cshtml)。 编辑器模板的内容将定义为:

@model MvcApplication14.Models.B

@Html.EditorFor(m => m.Name)

然后,修改主视图,使 EditorFor 如下所示:

@Html.EditorFor(model => model.ModelB, null, "")

“”指定我们的编辑器模板输出的任何字段该字段不会有名称前缀。因此,现在的输出将是:

<input class="text-box single-line" id="Name" name="Name" type="text" value="" /> 

但是,这现在将阻止绑定 ModelB,因此需要在后期操作中单独绑定并添加到我们的 A 模型中:

[HttpPost]
public ActionResult Index(A modelA, B modelB)
{
    modelA.ModelB = modelB;
    if (ModelState.IsValid)
    {
        return RedirectToAction("NextAction");
    }
    return View();
}

现在,当绑定modelB时,验证消息将使用键“”写入ModelState。因此,现在可以使用 @ValidationMessage() 例程来显示它。

警告:上述解决方法假设 modelB 没有与 modelA 相同的字段名称。例如,如果 modelBmodelA 都有一个 Name 字段,那么 DefaultModelBinder 可能无法将这些字段绑定到正确的位置等价物。例如,如果 A 模型还有一个名为 Name 的字段,则必须将其写入视图:

@Html.EditorFor(model => model.Name, null, "modelA.Name")

以确保其正确绑定。

希望这应该允许您使用 MVC3 框架中已定义的方法来实现所需的结果。

Lets start with what we know:

As the description suggests, if we have our models:

Model A:

public class A
{
    public B ModelB { get; set; }
}

Model B:

public class B : IValidatableObject
{
    public string Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> errors = new List<ValidationResult>();

        if (string.IsNullOrEmpty(Name)) {
            errors.Add(new ValidationResult("Please enter your name"));
        }

        return errors;
    }
}

And our view:

@model A

@Html.ValidationSummary(true)

@using (Html.BeginForm())
{
    @Html.EditorFor(model => model.ModelB.Name)

    <input type="submit" value="submit" />
}

Then the editor will output the line:

<input class="text-box single-line" id="ModelB_Name" name="ModelB.Name" type="text" value="" />

If we have our post action defined as:

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

Then when binding to the A model, the DefaultModelBinder will look for the property named ModelB.Name which it will find and bind to successfully.

However, the model validation performed by the DefaultModelBinder against the A model, will call the validation defined for Model B. This validation will return an error message which is not defined against a property but because this validation is part of a complex model it is added to the ModelState with the key of "ModelB".

When the ValidationSummary is called it looks for keys which are blank (i.e. defined for the model and not the property). As no blank keys exist, no error is displayed.

As a simple workaround, you could define an EditorTemplate for the B model.

In the folder containing your view, define a folder named EditorTemplates and in this create a view with the same name as the model (e.g. B.cshtml in my case). The contents of the Editor Template would be defined as:

@model MvcApplication14.Models.B

@Html.EditorFor(m => m.Name)

Then, modify the main view so that the EditorFor looks like:

@Html.EditorFor(model => model.ModelB, null, "")

The "" specifies that any fields output by our editor template will not have a name prefixed to the field. Therefore, the output will now be:

<input class="text-box single-line" id="Name" name="Name" type="text" value="" /> 

However, this will now prevent the binding of ModelB so this would need to be bound seperately in the post action and added to our A model:

[HttpPost]
public ActionResult Index(A modelA, B modelB)
{
    modelA.ModelB = modelB;
    if (ModelState.IsValid)
    {
        return RedirectToAction("NextAction");
    }
    return View();
}

Now, when the modelB is bound, the validation message will be written to the ModelState with the key "". Therefore, this can now be displayed with the @ValidationMessage() routine.

Warning: The above workaround assumes that modelB does not have the same field names as modelA. If both modelB and modelA had a Name field for example then the DefaultModelBinder may not bind the fields to their correct equivelents. For example, if the A model also has a field named Name then it would have to be written to the view as:

@Html.EditorFor(model => model.Name, null, "modelA.Name")

to ensure that it is bound correctly.

Hopefully, this should allow you to achieve the desired result using the methods already defined within the MVC3 framework.

烟花肆意 2024-11-23 16:23:03

对于那些发现这个问题的人,请看一下 ViewData.TemplateInfo.HtmlFieldPrefix (如其他答案中深入提到的)...

如果显式添加摘要级别验证错误,您通常可能会这样做(到root 对象)...

ModelState.AddModelError("", "This is a summary level error text for the model");

当为模型的属性添加类似摘要的验证错误时,该属性本身就是一个对象,您可以执行以下操作

ModelState.AddModelError("b", "This is a 'summary' error for the property named b");

: >b 是位于的属性的名称本身就是一种财产。

解释一下,直接添加摘要级别验证错误时,您只需指定对象属性的 HTML 前缀即可。

这可以使用 ViewData.TemplateInfo.HtmlFieldPrefix 获取。

For those who find this question, take a look at ViewData.TemplateInfo.HtmlFieldPrefix (as mentioned deep in other answers)...

If explicitly adding a summary level validation error you might usually do this (to the root object) ...

ModelState.AddModelError("", "This is a summary level error text for the model");

And when adding a summary-like validation error for a property of the model which is in itself an object you can do the following:

ModelState.AddModelError("b", "This is a 'summary' error for the property named b");

Where b is the name of the property which is in itself a property.

To explain, when adding a summary level validation error directly you can simply specify the HTML Prefix for the object property.

This can be obtained using ViewData.TemplateInfo.HtmlFieldPrefix.

埖埖迣鎅 2024-11-23 16:23:03

ModelState modelState = 默认(ModelState);
Model.TryGetValue(this.ViewData.TemplateInfo.HtmlFieldPrefix, out modelState);

var isExcludePropertyErrors = modelState != null;

ModelState modelState = default(ModelState);
Model.TryGetValue(this.ViewData.TemplateInfo.HtmlFieldPrefix, out modelState);

var isExcludePropertyErrors = modelState != null;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文