特定の状況で必須の検証属性を無効にする


138

特定のコントローラーアクションで必須検証属性を無効にできるかどうか疑問に思っていました。編集フォームの1つで、ユーザーが以前に指定したフィールドに値を入力する必要がないので、これは不思議に思っています。ただし、その後、値を入力するときに、値のハッシュなどの特別なロジックを使用してモデルを更新するロジックを実装します。

この問題を回避する方法に関する提案はありますか?

編集:
そして、はい、クライアントの検証はここで問題になります。値を入力しないとフォームを送信することができないからです。


3
+1良いQ.ここでクライアントの検証について言及すると良いでしょう。1つのオプションは、RequiredAttr完全に削除し、必要に応じてサーバー側のチェックを行うことです。しかし、これはクライアントにとってトリッキーです
ギデオン'20年

4
クライアント検証から特定のフィールドを無効にすることもカバーする人のためのポイント(jquery検証への参照は削除しない)
ギデオン

多分私はあなたの要点を逃しているかもしれませんが、ユーザーが事前に値をすでに指定している場合、それらの値はすでに存在しているため、必須の検証に合格します。他の意味ですか?
Erik Funkenbusch 2011年

パスワードやセキュリティの回答など、これらの値はハッシュ化されているため、編集フォームに新しい値を入力した場合は、挿入前に新しい値をハッシュし直しますが、オプションを空白のままにしておく必要があります。一種のこと。
Alex Hope O'Connor

1
@gideon:エイドリアン・スミスの回答をご覧ください:stackoverflow.com/a/9781066/114029
Leniel Maccaferri

回答:


76

この問題は、ビューモデルを使用して簡単に解決できます。ビューモデルは、特定のビューのニーズに合わせて特別に調整されたクラスです。したがって、たとえば、あなたの場合、次のビューモデルを使用できます。

public UpdateViewView
{
    [Required]
    public string Id { get; set; }

    ... some other properties
}

public class InsertViewModel
{
    public string Id { get; set; }

    ... some other properties
}

これは、対応するコントローラーアクションで使用されます。

[HttpPost]
public ActionResult Update(UpdateViewView model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
    ...
}

nullにできないブール値/ビットがある場合、常にtrueになるとは限りません。しかし、それが真または偽になるため、実際には違いはありません。必要なフィールドを強調表示するcssがあり、誤ってCheckBoxForアイテムを強調表示しました。私の解決策:$( "#IsValueTrue")。removeAttr( "data-val-required");
Robert Koch

更新では、一般的に(FormCollectionコレクション)があります。パラメータとしてモデルをどのように使用しているか説明してください
Raj Kumar

4
いいえ、通常はありませんUpdate(FormCollection collection)。少なくとも私は持っていません。私は常に特定のビューモデルを定義して使用しますUpdate(UpdateViewView model)
Darin Dimitrov

同じHTTPアクションを持つメソッドのオーバーロードは許可されないので、私にはこれは機能しないようです。何か不足していますか?
e11s

@edgi、いや、あなたは何も見逃していません。それは私の投稿の間違いです。2番目のアクションメソッドは明らかに呼び出されInsertます。これを指摘してくれてありがとう。
Darin Dimitrov

55

クライアント側で単一のフィールドの検証を無効にするだけの場合は、次のように検証属性をオーバーライドできます。

@Html.TexBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})

20
jQueryを介して私のために働いたもの:$( "#SomeValue")。removeAttr( "data-val-required");
Robert Koch

6
私はこのアプローチが好きですが、次のコマンドを使用してフォーム検証属性を再解析する必要があります。$( 'form')。removeData( 'unobtrusiveValidation'); $( 'form')。removeData( 'validator'); $ .validator.unobtrusive.parse( 'フォームのセレクター');
Yannick Smits

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })-IMOが読みやすくなりました。
eth0

このアプローチのほとんどで各フィールドを細かく制御できるようにしたいのですが、POSTで保存するデータを送信するときにModelState.IsValidをキャンセルして追加することもできます。多分いくつかのリスクを引き起こすのでしょうか?
フェリペFMMobile 2012

4
jQueryで無効にしたい場合:$(".search select").attr('data-val', false);
Leniel Maccaferri

40

私はこの質問がずっと前に回答されており、受け入れられた回答が実際に機能することを知っています。しかし、気になることが1つあります。検証を無効にするためだけに2つのモデルをコピーする必要があります。

これが私の提案です:

public class InsertModel
{
    [Display(...)]
    public virtual string ID { get; set; }

    ...Other properties
}

public class UpdateModel : InsertModel
{
    [Required]
    public override string ID
    {
        get { return base.ID; }
        set { base.ID = value; }
    }
}

この方法では、クライアント/サーバー側の検証に煩わされる必要はありません。フレームワークは想定どおりに動作します。また、[Display]基本クラスで属性を定義する場合、属性を再定義する必要はありませんUpdateModel

また、これらのクラスを同じ方法で使用できます。

[HttpPost]
public ActionResult Update(UpdateModel model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertModel model)
{
    ...
}

特に検証属性のリストが長い場合は、このアプローチの方が好きです。あなたの例では、基本クラスにさらに属性を追加して、利点をより明確にすることができます。私が目にできる唯一の欠点は、プロパティを継承して属性をオーバーライドする方法がないことです。たとえば、基本クラスに[必須]プロパティがある場合、カスタム[オプション]属性がない限り、継承プロパティも強制的に[必須]になります。
Yorro

複数の属性を持つ 'Project'オブジェクトを持つviewModelがあり、特定の状況でこれらの属性の1つのみを検証したいのですが、私もこのようなものを考えました。オブジェクトの属性をオーバーライドできるとは思いませんか?何かアドバイス?
vincent de g

属性を上書きすることはできません。基本クラスには、すべてのサブクラスに共通の属性のみを含める必要があります。次に、サブクラスで必要な属性を定義する必要があります。
PhilDulac 2014年

1
最もエレガントで再利用可能な明確なソリューション。複製は悪いです。多態性が道です。+1
T-moty

私の場合、基本クラスには必須属性があり、親クラスでは必須属性にしたくないです。モデルの2つのコピーがなくても可能ですか?
Alexey Strakh 16

27

コントローラーアクションで次のようにすると、プロパティからすべての検証を削除できます。

ModelState.Remove<ViewModel>(x => x.SomeProperty);

MVC5に関する@Ian コメント

以下はまだ可能です

ModelState.Remove("PropertyNameInModel");

更新されたAPIを使用すると、静的な型付けが失われることに少し不愉快です。HTMLヘルパーのインスタンスを作成し、NameExtensionsメソッドを使用することにより、古い方法と同様のことを実現できます。


ただし、ModelStateそのシグネチャに一致するメソッドはありません。少なくともMVC 5にはありません。
Ian Kemp

問題は、すべての検証を削除する方法ではありませんでした。必須フィールドの検証を削除する方法でした。他の検証ルールを残したまま、これを行うことができます。
Richard

15

クライアント側 フォームの検証を無効にするために、私の調査に基づく複数のオプションを以下に示します。それらの1つがうまくいけばうまくいくでしょう。

オプション1

私はこれを好み、これは私にとっては完璧に機能します。

(function ($) {
    $.fn.turnOffValidation = function (form) {
        var settings = form.validate().settings;

        for (var ruleIndex in settings.rules) {
            delete settings.rules[ruleIndex];
        }
    };
})(jQuery); 

のように呼び出す

$('#btn').click(function () {
    $(this).turnOffValidation(jQuery('#myForm'));
});

オプション2

$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");

オプション3

var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";

オプション4

 $("form").get(0).submit();
 jQuery('#createForm').unbind('submit').submit();

オプション5

$('input selector').each(function () {
    $(this).rules('remove');
});

サーバ側

属性を作成し、その属性でアクションメソッドをマークします。これをカスタマイズして、特定のニーズに適合させます。

[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var modelState = filterContext.Controller.ViewData.ModelState;

        foreach (var modelValue in modelState.Values)
        {
            modelValue.Errors.Clear();
        }
    }
}

より良いアプローチはここで説明されています動的にMVCサーバー側検証を有効/無効にします


$( 'input selector')。each(function(){$(this).rules( 'remove');}); 助けてくれました
Sachin Pakale

問題は、必須のフィールド検証を削除することではなく、すべての検証を削除することでした。あなたの答えは、すべての検証を削除することです。
リチャード

14

個人的には、Darin Dimitrovが彼のソリューションで示したアプローチを使用する傾向があります。これにより、検証でデータ注釈アプローチを使用できるようになり、手元のタスクに対応する各ViewModelに個別のデータ属性を持つことができます。モデルとビューモデル間でコピーする作業量を最小限に抑えるには、AutoMapperまたはValueInjecterを調べる必要があります。どちらもそれぞれに長所があるのでチェックしてみてください。

IValidatableObjectからビューモデルまたはモデルを派生させることもできます。これにより、関数Validateを実装するオプションが提供されます。検証では、ValidationResult要素のリストを返すか、検証で検出した問題ごとに利回りを返すことができます。

ValidationResultは、エラーメッセージとフィールド名を含む文字列のリストで構成されます。エラーメッセージは、入力フィールドに近い場所に表示されます。

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
  if( NumberField < 0 )
  {
    yield return new ValidationResult( 
        "Don't input a negative number", 
        new[] { "NumberField" } );
  }

  if( NumberField > 100 )
  {
    yield return new ValidationResult( 
        "Don't input a number > 100", 
        new[] { "NumberField" } );
  }

  yield break;
}

これをクライアント側でフックできますか?
ムハンマドアディールザヒド

通常、クライアント側の検証は個々のフィールドに対してのみ行われ、ユーザーの利便性として、インタラクティブなフィールドごとの検証フィードバックを実行します。通常、オブジェクト検証ステップには依存検証(オブジェクト自体の外部にある複数のフィールドや条件)が含まれるため、JavaScriptにコードをコンパイルできたとしても、クライアント側でこれを実行できるとは限りません。クライアント側での複雑な/依存検証があなたのケースで価値を追加する場合、onsubmit-callbackを使用し、クライアント側で検証ロジックを複製する必要があります。
mindplay.dk 2012年

7

ここで最もクリーンな方法は、クライアント側の検証を無効にし、サーバー側で次のことを行う必要があると考えています。

  1. ModelState ["SomeField"]。Errors.Clear(コントローラー内、またはコントローラーコードが実行される前にエラーを削除するアクションフィルターを作成)
  2. 検出された問題の違反を検出したら、コントローラーコードからModelState.AddModelErrorを追加します。

これらの「回答済み」フィールドの数は異なる可能性があるため、ここでのカスタムビューモデルでも問題は解決しないようです。そうでない場合は、カスタムビューモデルが実際に最も簡単な方法である可能性がありますが、上記の手法を使用すると、検証の問題を回避できます。


1
これはまさに私が必要としたものです。1つのビューがあり、このビューで別のアクションが発生したため、別のViewModelでは作成できませんでした。これは魅力のように機能します
ggderas

6

これはコメントで他の誰かの答えでした...しかし、それは本当の答えでなければなりません:

$("#SomeValue").removeAttr("data-val-required")

[Required]属性を持つフィールドを使用してMVC 6でテスト済み

上記のhttps://stackoverflow.com/users/73382/robから盗まれた答え


1
サーバー側の検証はどうですか?
T-moty

ModelState.Remove、そうですか?とにかく、私にとって問題は、モデルに2番目のエンティティが含まれていることでした...プライマリを検証したいが、セカンダリはそのページで検証を必要としませんでした...したがって、このシナリオでは、JQueryのみが必要でした。
Mike_Matthews_II 2015年

リンク切れと思いますので、編集してください。これは部分的な回答です
T-moty

これがMVC6で機能する(奇妙なことに、現在MVC6でテストするオプションはありません)と言っていて、現在使用しているMVC4で機能しないのは奇妙です。
eaglei22

質問はMVC / C#に対するもので、JSではありません。答えはサーバー側では機能しません
mtbennett

2

モデルの編集ビューを作成しているときにこの問題が発生し、フィールドを1つだけ更新したいのですが。

最も簡単な方法の私の解決策は、2つのフィールドを使用して配置することです:

 <%: Html.HiddenFor(model => model.ID) %>
 <%: Html.HiddenFor(model => model.Name)%>
 <%: Html.HiddenFor(model => model.Content)%>
 <%: Html.TextAreaFor(model => model.Comments)%>

コメントは、編集ビューでのみ更新するフィールドであり、必須属性がありません。

ASP.NET MVC 3エンティティ


1

私の知る限り、実行時に属性を削除することはできませんが、それらの値を変更するだけです(つまり、readonly true / false)。ここで同様の何かを探してください。属性をいじらずに必要なことを行う別の方法として、特定のアクションのViewModelを使用して、他のコントローラーが必要とするロジックを壊すことなくすべてのロジックを挿入できるようにします。ある種のウィザード(マルチステップフォーム)を取得しようとする場合は、代わりに既にコンパイル済みのフィールドをシリアル化し、TempDataを使用してそれらをステップに沿って持ってくることができます。(シリアライズのデシリアライズを支援するために、MVCフューチャーを使用できます)


1

@Darinが言ったことは、私もお勧めするものです。ただし、それに加えて(コメントの1つに応じて)、ビット、ブール、Guidなどのプリミティブ型にもこのメソッドを使用して、単にnull可能にすることもできます。これを行うと、Required属性は期待どおりに機能します。

public UpdateViewView
{
    [Required]
    public Guid? Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public int? Age { get; set; }
    [Required]
    public bool? IsApproved { get; set; }
    //... some other properties
}

1

MVC 5以降、これをに追加することで簡単に実現できますglobal.asax

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

1

Web APIの挿入と更新に同じモデルを使用できるソリューションを探していました。私の状況では、これは常にボディコンテンツです。[Requiered]それはupdateメソッドである場合、属性はスキップされなければなりません。私のソリューションでは[IgnoreRequiredValidations]、メソッドの上に属性を配置します。これは次のとおりです。

public class WebServiceController : ApiController
{
    [HttpPost]
    public IHttpActionResult Insert(SameModel model)
    {
        ...
    }

    [HttpPut]
    [IgnoreRequiredValidations]
    public IHttpActionResult Update(SameModel model)
    {
        ...
    }

    ...

他に何をする必要がありますか?起動時に独自のBodyModelValidatorを作成して追加する必要があります。これはHttpConfigurationにあり、次のようになります。config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());

using Owin;
using your_namespace.Web.Http.Validation;

[assembly: OwinStartup(typeof(your_namespace.Startup))]

namespace your_namespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Configuration(app, new HttpConfiguration());
        }

        public void Configuration(IAppBuilder app, HttpConfiguration config)
        {
            config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
        }

        ...

私自身のBodyModelValidatorは、DefaultBodyModelValidatorから派生しています。そして、私は 'ShallowValidate'メソッドをオーバーライドする必要があることを理解します。このオーバーライドでは、必要なモデルバリデーターをフィルターします。そして今、IgnoreRequiredOrDefaultBodyModelValidatorクラスとIgnoreRequiredValidations属性クラス:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;

namespace your_namespace.Web.Http.Validation
{
    public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
    {
        private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;

        static IgnoreRequiredOrDefaultBodyModelValidator()
        {
            _ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
        }

        protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
        {
            var actionContext = validationContext.ActionContext;

            if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
                validators = validators.Where(v => !v.IsRequired);          

            return base.ShallowValidate(metadata, validationContext, container, validators);
        }

        #region RequiredValidationsIsIgnored
        private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
        {
            bool ignore;

            if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
                _ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));

            return ignore;
        }

        private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
                return false;

            return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
        } 
        #endregion
    }

    [AttributeUsage(AttributeTargets.Method, Inherited = true)]
    public class IgnoreRequiredValidationsAttribute : Attribute
    {

    }
}

出典:



0

私の場合、同じモデルが多くのページで再利用のために使用されました。だから私がしたことは、除外をチェックするカスタム属性を作成したことです

public class ValidateAttribute : ActionFilterAttribute
{
    public string Exclude { get; set; }
    public string Base { get; set; }
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!string.IsNullOrWhiteSpace(this.Exclude))
        {
            string[] excludes = this.Exclude.Split(',');
            foreach (var exclude in excludes)
            {
                actionContext.ModelState.Remove(Base + "." + exclude);
            }
        }
        if (actionContext.ModelState.IsValid == false)
        {
            var mediaType = new MediaTypeHeaderValue("application/json");
            var error = actionContext.ModelState;

            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);

        }
    }
}

そしてあなたのコントローラーで

[Validate(Base= "person",Exclude ="Age,Name")]
    public async Task<IHttpActionResult> Save(User person)
    {

            //do something           

    }

モデルは言う

public class User
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Range(18,99)]
    public string Age { get; set; }
    [MaxLength(250)]
    public string Address { get; set; }
}

-1

はい、必須属性を無効にすることができます。RequiredAtributeから拡張して独自のカスタムクラス属性(ChangeableRequiredと呼ばれるサンプルコード)を作成し、Disabledプロパティを追加し、IsValidメソッドをオーバーライドして、無効化されているかどうかを確認します。次のように、リフレクションを使用して無効化されたpopertyを設定します。

カスタム属性:

namespace System.ComponentModel.DataAnnotations
{
    public class ChangeableRequired : RequiredAttribute
    {
       public bool Disabled { get; set; }

       public override bool IsValid(object value)
       {
          if (Disabled)
          {
            return true;
          }

          return base.IsValid(value);
       }
    }
}

プロパティを更新して、新しいカスタム属性を使用します。

 class Forex
 {
 ....
    [ChangeableRequired]
    public decimal? ExchangeRate {get;set;}
 ....
 }

プロパティを無効にする必要がある場合は、リフレクションを使用して設定します。

Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib =  (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];

// Set Attribute to true to Disable
attrib.Disabled = true;

これはすてきで清潔な感じですか?

注:上記の検証は、オブジェクトインスタンスがalive \ activeである間は無効になります...


これについてのいくつかの議論の後、私は検証を無効にすることはお勧めしませんが、むしろルールを確認することをお勧めします。無効にした場合、依存関係を作成してルールを再度有効にするので、ルールを確認することをお勧めします。
アーネストガニング

同じプロセス内で実行されるそのフィールドのすべての検証を完全に無効にするために静的な値を変更することは、ひどい考えのように聞こえます。
ジェレミーレイクマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.