web-dev-qa-db-ja.com

ASP.NET MVC3でフォームを検証するときに(POCOの)一部のプロパティを無視する方法はありますか?

新規ユーザー登録用のサインアップウィザードがあります。 2ページ目に移動しようとすると、Userオブジェクトがまだ完全に入力されていないため、検証エラーが発生します。 ModelState.IsValidチェックをチェックするときに、いくつかのプロパティを無視するように各ActionMethodに指示できる方法はありますか?

例えば。 (簡略化されたpseduoコード)

public class User
{
   [Required]
   public string Name; // Asked on page 1.
   [Required]
   public int Age; // Asked on page 1.
   [Required]
   public string Avatar;  // Asked on Page 2.
}

アバターは必須である/ nullにはできないと不満を言う。しかし、次のページまで、ユーザーにこれを入力するよう依頼する機会がありません。

1ページで、このチェックを無視するように依頼することはできますか?

22
Pure.Krome
22
Linkgoron

アクションでは、まだチェックされていないアイテムのエラーを削除するだけです。これにより、すでにチェックされているアイテムに対してモデルが有効になります

foreach (var error in ModelState["Avatar"].Errors)
 {
      ModelState["Avatar"].Errors.Remove(error);
 }

または

ModelState["Avatar"].Errors.Clear();
20
Darroll

ModelStateのプロパティを無視するには、次のコードが最も簡単です。

if (ModelState["PropertyName"] != null) ModelState["PropertyName"].Errors.Clear();
6
Shyam Bhagat

これについては、Steve Sandersonのasp.net mvc 2ブックの486ページで説明されています。

ActionFilterAttributeから継承するカスタム属性ValidateIncomingValuesOnlyAttributeを作成し、それをコントローラークラスに適用します。

OnActionExecutingメソッドをオーバーライドします。

public override void OnActionExecuting(ActionExecutingContext filterContext)
{

var modelState = filterContext.Controller.ViewData.ModelState;
var incomingValues = filterContext.Controller.ValueProvider;

var keys = modelState.Keys.Where(x => !incomingValues.ContainsPrefix(x));
foreach(var key in keys)
{
modelState[key].Errors.Clear();
}
}

このようにして、ウィザードの各ステップにのみ関連するデータを検証します。次に、検証されたデータをサーバーに送信するために、データ入力のない確認ページが必要です。

しかし、何よりも、Steve Sandersonの本を読んでください。これは、これと他の問題の実用的な解決策を提供します。

補遺:

上記の代わりにビューモデルにマップする場合は、次のいずれかを行う必要があるので注意してください。

a。検証データ注釈属性でviewmodelプロパティを装飾しないでください。この場合、ユーザーがウィザード全体を入力してデータベースに送信しようとしたときにのみ検証します。これは、ユーザーの観点からは非常に厄介です...

b。それ以外の場合は、S Sandersonによって説明されている手法を使用する必要があります。つまり、現在のステップのフィールドに関係しない検証エラーをクリアします。

受け入れられた回答が、質問されたとおりの質問への回答とは見なされません。

5
awrigley

私は検証フォームとModelStateをいじっていて、新しいメソッドやオーバーライドなどを記述せずに、問題の非常に簡単な解決策を見つけました。

ModelState.Where(m => m.Key == "Avatar").FirstOrDefault().Value.Errors.Clear();
// At  this point ModeState will have an error for that Key,
// by applying Clear it remove the error so modelstate becomes valid again

if (!ModelState.IsValid) {
    return View("User", model);
} else {     
    try  {
        // do something
    } catch {
        TempData["errorMessage"] = "something went wrong";
    }
}
2
Adam Bielecki

IgnoreModelErrorsカスタムクラスはどうですか?

http://mrbigglesworth79.blogspot.in/2011/12/partial-validation-with-data.html


上記のリンクで示されているように、ActionFilterAttributeクラスから継承し、OnActionExecutingの[一致する名前または正規表現パターンに基づいて]エラーをクリアします。これはよりクリーンになります。

1
Hoven
public override void OnActionExecuting(ActionExecutingContext context)
{
    var modelstate = context.ModelState;
    var keys = modelstate.Keys.Where(x => ExculdeFeilds.Split(",").ToList().Contains(x));
    foreach (var item in keys)
    {
        modelstate[item].ValidationState = ModelValidationState.Valid;
    }
    if (!modelstate.IsValid)
    {
        context.Result = new BadRequestObjectResult(context.ModelState);
    }
}
0
Varun Anand

ポストバックされるデータと正確に一致するViewModelは、非常に予測可能であり、強力な型付け、足場などのすべての利点を得ることができるため、一般的に推奨される手法です。一方、BindAttributeを使用すると、アカウントにポストバックされないため、プロパティ名が変更されてもBindAttribute IncludeまたはExclude文字列が変更されない場合、実行時にサイレントエラーが発生する可能性があります。検証属性の使用を回避すると、MVCに多くの欠点があり、IValidatableObjectやFluentValidationなどの他の検証手法に置き換える必要があります。

ViewModelsのすべての利点とBindAttributeに付随する警告にもかかわらず、BindAttributeを使用し、モデル/ビューモデルに部分的にポストすることが望ましい場合があります。このActionFilterAttributeは、その正確なケースをカバーします。 @awrigleyはさらに一歩進んだコードを使用しますが、ValueProviderに基づいてエラーをクリアするのではなく、BindAttributeの使用(たとえば、IncludeおよびExclude)に基づいてエラーをクリアします。この属性は、BindAttributeが適用されていない場合のMVC検証の動作を変更しないため、GlobalFilterCollectionに安全に追加できます。注意:私はこれを多用していませんが、基本的なケースではうまく機能します。

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;

/// <summary>
/// When the BindAttribute is in use, validation errors only show for values that 
/// are included or not excluded.
/// </summary>
public class ValidateBindableValuesOnlyAttributes : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var modelState = filterContext.Controller.ViewData.ModelState;
        var includedProperties = filterContext.ActionDescriptor.GetParameters()
            .SelectMany(o => o.BindingInfo.Include.Select(name => (string.IsNullOrWhiteSpace(o.BindingInfo.Prefix) ? "" : o.BindingInfo.Prefix + ".") + name));
        var excludedProperties = filterContext.ActionDescriptor.GetParameters()
            .SelectMany(o => o.BindingInfo.Exclude.Select(name => (string.IsNullOrWhiteSpace(o.BindingInfo.Prefix) ? "" : o.BindingInfo.Prefix + ".") + name));

        var ignoreTheseProperties = new List<KeyValuePair<string, ModelState>>();
        if (includedProperties.Any())
        {
            ignoreTheseProperties.AddRange(modelState.Where(k => !includedProperties.Any(name => Regex.IsMatch(k.Key, "^" + Regex.Escape(name) + @"(\.|\[|$)"))));
        }
        ignoreTheseProperties.AddRange(modelState.Where(k => excludedProperties.Any(name => Regex.IsMatch(k.Key, "^" + Regex.Escape(name) + @"(\.|\[|$)"))));

        foreach (var item in ignoreTheseProperties)
        {
            item.Value.Errors.Clear();
        }
    }
}
0
Jeremy Cook

検証されることになっていない参照エンティティがありました。

アクションの最初の検証からそれを削除しました:

[HttpPost]
public async Task<IActionResult> Post([FromBody] Contact contact)
{
  var skipped = ModelState.Keys.Where(key => key.StartsWith(nameof(Contact.Portfolios)));
  foreach (var key in skipped)
    ModelState.Remove(key);
    //ModelState doesn't include anything about Portfolios which we're not concerned with

  if (!ModelState.IsValid)
    return BadRequest(ModelState);

  //Rest of action
}
0
Shimmy