5

私のモデル(クラス A )には、タイプ B のプロパティ( b と呼ばれる)がIValidatableObject実装されています。

ビューは持っています@Html.ValidationSummary(true)

検証の概要で、プロパティに関連するエラーを除外したいと考えています。クラスBのIValidatableObject実装ではValidationResult、memberNamesなしで返されます

IValidatableObjectただし、クラス B はクラス A のプロパティであるため、クラス B の検証エラーは表示されません。

クラス B の非プロパティ検証エラーを表示するには?

4

6 に答える 6

3

これはかなり簡単だと思います。例を挙げて説明しましょう。 まず、あなたが直面している問題を作成させてください。次に、解決方法を説明します。

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)MyPropertyに長さが10を超える文字列を入力すると、「MaxLengthに到達しました」というエラーメッセージが表示されますが、エラーは表示されません:) :)

ここに画像の説明を入力してください

この理由

したがって、ビューのコードを見ると、次の行を見つけることができます

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

CBPropはClassAのプロパティであるため、CBPropのValidationSummary(true)エラーはすべて除外されます。そのため、表示されているエラーメッセージは見つかりません。ただし、これにはいくつかのオプションがあります。

オプション

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

エラーは次のようになります

ここに画像の説明を入力してください

エラーメッセージが表示されなかった理由と、使用可能なオプションは明らかだと思います。あなたに最適なアプローチを決定するのはあなた次第です。

乾杯

于 2012-05-06T18:57:42.110 に答える
2

この検証サマリーの動作はあなたの場合には不適切かもしれませんが、一般的には「正しい」と見なす必要があります。モデルに含まれるサブオブジェクトでエラーが作成されると、正しいプレフィックスがエラーに追加されます。つまり、サブオブジェクトがプロパティに含まれているMyProp場合、プレフィックスMyPropはすべてのエラーに自動的に追加されます。これは、作成されるすべてのエラーに正しい名前を付けるために必要です。これがないと、Validationsummaryも もValidationMessageFor正しく機能しません。エラーは完全な名前 (プレフィックス全体を含むもの) を参照するためです。Name2 つの異なるサブオブジェクトに 2つのプロパティがある可能性があるため、これがあいまいさを回避する唯一の方法です。

ただし、多くの場合、この「正しい」動作は、サブオブジェクトで生成されたエラーが単純なプロパティ レベルのエラーではなく、「オブジェクト全体」レベルのエラーである場合には不適切です。そのような場合、一般的な検証の要約にそれらを表示したいと思うかもしれません。

この問題には、次の 2 つの方法で直面できます。

  1. サブオブジェクトに固有の別の検証概要を使用する
  2. エラー バブリング - 私はよくエラー バブリングを使用して、モデルのサブパーツ (画面には表示されていません) にエラーが含まれていることを通知します。これにより、ユーザーは詳細ウィンドウ (jQuery ダイアログなど) を開いてエラーを確認できます。基本的に、エラー バブリングは、ModelState 内のすべてのエラーを で処理しforeach、それらの一部を昇格させることで構成されます。上に昇格するとは、エラー接頭辞の最後の部分を削除することを意味します。エラーをプロモートするときは、元のエラーを保持するかどうかに関係なく、元のエラーを保持する方が簡単であり、ほとんどの場合、それは正しいことです。すべてのエントリをループしている間はエントリを削除できないことに注意してください。リストに入れ、ループが終了した後に削除する必要があります。

プロモート基準は、ニーズによって異なる場合があります。例を挙げます:

  • プロパティ レベルのエラーを昇格させると、オブジェクト レベルのエラーに変換されます。
  • サブオブジェクト レベルのエラーを昇格させると、外部オブジェクトのオブジェクト レベルのエラーに変換されます。これは興味深いケースですViewModel。単純な値ではなく、オブジェクト全体を含むルートのプロパティに関連付けられたオブジェクト レベルのエラーを促進するだけです。

エラー処理は、定義できるカスタム ActionFilter 内で実行でき、いくつかのアクション メソッドで再利用できます。

以下は、単純な PromoteAttribute ActionFilter のコードです。その用途は次のとおりです。

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

つまり、ルート モデルに昇格させたい式エラーのリストを渡し、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);

            }
        }
    }
}
于 2012-05-04T16:20:22.253 に答える
0

私たちが知っていることから始めましょう:

説明が示すように、モデルがある場合:

モデル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="" />

ポストアクションを次のように定義している場合:

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

次に、Aモデルにバインドするときに、が検索して正常にバインドするDefaultModelBinder名前のプロパティModelB.Nameを探します。

ただし、モデルDefaultModelBinderに対して実行されるモデル検証は、Aモデルに対して定義された検証を呼び出しますB。この検証は、プロパティに対して定義されていないエラーメッセージを返しますが、この検証は複雑なモデルの一部であるため、「ModelB」のキーを使用してModelStateに追加されます。

が呼び出されると、ValidationSummary空白のキーが検索されます(つまり、プロパティではなくモデルに対して定義されます)。空白キーが存在しないため、エラーは表示されません。

EditorTemplate簡単な回避策として、Bモデルのを定義できます。

ビューを含むフォルダーで、という名前のフォルダーを定義し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されるため、postアクションで個別にバインドし、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ます。たとえば、modelBとの両方にフィー​​ルドmodelAがある場合、はフィールドを正しい同等物にバインドしない可能性があります。たとえば、モデルに次の名前のフィールドもある場合は、次のようにビューに書き込む必要があります。NameDefaultModelBinderAName

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

正しくバインドされていることを確認します。

うまくいけば、これにより、MVC3フレームワーク内ですでに定義されているメソッドを使用して目的の結果を達成できるはずです。

于 2012-05-04T10:35:08.927 に答える
0

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;
}
于 2012-05-03T14:29:19.403 に答える
0

この質問を見つけた人は、ViewData.TemplateInfo.HtmlFieldPrefix(他の回答で深く言及されているように)を見てください...

要約レベルの検証エラーを明示的に追加する場合は、通常、これを (ルートオブジェクトに対して) 行うことができます ...

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

于 2014-03-17T12:14:17.927 に答える