Вимкнути необхідний атрибут перевірки за певних обставин


138

Мені було цікаво, чи можна відключити атрибут Необхідна перевірка в певних діях контролера. Мені це цікаво, оскільки в одній із моїх форм редагування я не вимагаю від користувача вводити значення для полів, які вони вже вказали раніше. Однак я реалізую логіку, що коли вони вводять значення, вона використовує якусь спеціальну логіку для оновлення моделі, наприклад хешування значень тощо.

Будь-які судження про те, як подолати цю проблему?

EDIT:
І так, перевірка клієнта тут є проблемою, оскільки це не дозволить їм надіслати форму без введення значення.


3
+1 добре Q. Було б добре згадати тут перевірку клієнта. Один із варіантів - повністю видалити RequiredAttrта зробити перевірку на стороні сервера, коли потрібно. Але це буде хитро для клієнта
Гедеон

4
Бали для всіх, хто також охоплює відключення певних полів від перевірки клієнта (не видаляючи посилання на перевірку jquery)
gideon

Можливо, я пропускаю вашу точку, але якщо користувач уже вказав ці значення заздалегідь, то ці значення вже є, і тому вони пройдуть необхідну перевірку. Ви мали на увазі щось інше?
Ерік Функенбуш

Оскільки ці значення з тих пір були хешовані, такі як пароль та відповідь на безпеку, тому, якщо вони вводять нове значення у формі редагування, я хочу повторно хешувати нове значення перед вставкою, але я також хочу, щоб параметр його залишався порожнім щось таке.
Алекс Хоуп О'Коннор

1
@gideon: см Едріана Сміта Відповідь: stackoverflow.com/a/9781066/114029
Leniel Маккаферрі

Відповіді:


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)
{
    ...
}

Не завжди вірно, якщо у вас є булева / бітова, яка не є нульовою. Але насправді це не має ніякого значення, оскільки це закінчиться справжнім чи хибним. У мене був css, щоб виділити обов'язкові поля, і він помилково виділив елемент CheckBoxFor. Моє рішення: $ ("# IsValueTrue"). RemoveAttr ("data-val-обов'язковий");
Роберт Кох

Оновлення ми зазвичай маємо (колекція FormCollection). чи можете ви поясніть, як ви використовуєте модель як параметр
Радж Кумар

4
Ні, у нас зазвичай немає Update(FormCollection collection), принаймні, я ніколи цього не роблю. Я завжди визначити і використовувати конкретну модель уявлення: Update(UpdateViewView model).
Дарин Димитров

Методи перевантаження з однаковою дією HTTP заборонені, тому мені здається, що це не буде працювати. Я щось пропускаю?
e11s

@edgi, ні, ти нічого не пропускаєш. Це помилка в моєму дописі. Очевидно, слід назвати другий метод дії Insert. Дякуємо, що вказали на це.
Дарин Димитров

55

Якщо ви просто хочете вимкнути перевірку для одного поля на стороні клієнта, ви можете змінити атрибути перевірки таким чином:

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

20
Що працювало для мене через jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-Required");
Роберт Кох

6
Мені подобається такий підхід, але мені потрібно було проаналізувати атрибути перевірки форми, використовуючи: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ('форма'). deleteData ('валідатор'); $ .validator.unobtrusive.parse ("селектор для вашої форми");
Яник Сміт

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- легше читати ІМО.
eth0

Мені найбільше сподобався цей підхід, щоб я міг чітко контролювати кожне поле, але ви можете додати скасувати ModelState.IsValid, коли відправляти дані для збереження POST. Може викликати певний ризик?
Феліпе FMMobile

4
Якщо ви хочете відключити його через jQuery:$(".search select").attr('data-val', false);
Леніел Макаферрі

40

Я знаю, що на це питання було дано відповідь давно, і прийнята відповідь справді зробить роботу. Але є одне, що мене турбує: потрібно копіювати дві моделі лише для відключення перевірки.

Ось моя пропозиція:

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)
{
    ...
}

Мені подобається такий підхід, особливо якщо у вас довгий список атрибутів перевірки. У вашому прикладі ви можете додати більше атрибутів у базовий клас, щоб вигода стала більш очевидною. Єдиний недолік, який я можу бачити, - це те, що немає способу успадкування властивостей переотримати атрибути. Наприклад, якщо у базового класу є властивість [Обов’язково], спадкове властивість також змушене бути [Потрібно], якщо у вас є спеціальний атрибут [Необов’язковий].
Йорро

Я теж думав про щось подібне, хоча у мене є viewModel, який має об’єкт "Project", який має кілька атрибутів, і мені хочеться лише одного з цих атрибутів перевірити за певних обставин. Я не думаю, що я можу просто змінити атрибут об'єкта, правильно? будь-яка порада?
vincent de g

Ви не можете змінити атрибут. Базовий клас повинен містити лише загальні атрибути для всіх підкласів. Потім ваші підкласи повинні визначити необхідні їм атрибути.
PhilDulac

1
Найбільш елегантне, багаторазове, чітке рішення. Реплікації погані. Поліморфізм - це шлях. +1
T-moty

у моєму випадку базовий клас має необхідний атрибут, і я хочу зробити його непотрібним у своєму батьківському класі. Це можливо без наявності двох примірників моделі?
Олексій Страх

27

Ви можете видалити всю перевірку властивості з наступного в дії контролера.

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

@ Коментар Яна щодо MVC5

Наступне ще можливо

ModelState.Remove("PropertyNameInModel");

Трохи прикро, що ви втрачаєте статичне введення тексту за допомогою оновленого API. Ви можете досягти чогось подібного до старого способу, створивши екземпляр помічника HTML та використовуючи NameExtensions Methods .


За винятком ... не існує жодного методу, ModelStateякий відповідає цьому підпису. Принаймні, не в MVC 5.
Ян Кемп

Питання полягало не в тому, як видалити всю перевірку. Як видалити необхідну перевірку поля. Ви можете зробити це, залишивши інші правила перевірки.
Річард

15

Сторона клієнта Щоб вимкнути перевірку форми, нижче наведено кілька варіантів, заснованих на моїх дослідженнях. Один із них, сподіваємось, працює для вас.

Варіант 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 на динамічному рівні


$ ('селектор введення'). кожен (функція () {$ (це) .rules ('видалити');}); допоміг мені
Сахін Пакале

Питання стосувалося конкретно про видалення необхідної перевірки поля, а не про видалення всієї перевірки. Ваша відповідь стосується видалення всіх перевірок.
Річард

14

Особисто я б схильний використовувати підхід, який Дарин Димитров показав у своєму рішенні. Це звільняє вас від можливості використовувати підхід до анотації даних з валідацією І мати окремі атрибути даних у кожному 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. Якщо складна / залежна валідація на стороні клієнта додає значення у вашому випадку, вам потрібно буде використовувати зворотний виклик і дублювати логіку перевірки на стороні клієнта.
mindplay.dk

7

Тут я вважаю, що найясніший спосіб відключити перевірку вашої клієнтської сторони, і на стороні сервера вам потрібно буде:

  1. ModelState ["SomeField"]. Errors.Clear (у своєму контролері або створити фільтр дій для видалення помилок перед виконанням коду контролера)
  2. Додайте ModelState.AddModelError з коду контролера, коли ви виявите порушення виявлених проблем.

Здається, навіть власна модель перегляду тут не зможе вирішити проблему, оскільки кількість полів, які попередньо відповіли, може змінюватися. Якщо цього не зробити, то модель власного перегляду справді може бути найпростішим способом, але, використовуючи вищезазначену методику, ви зможете обійти проблеми з валідацією.


1
Це саме те, що мені було потрібно. У мене було одне перегляд і різні дії відбуваються в цьому поданні, тому я не міг зробити це з диференційованими ViewModels. Це працює як принадність
ggderas

6

це була чужа відповідь у коментарях ... але це повинна бути справжня відповідь:

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

перевірено на MVC 6 із полем, що має [Required]атрибут

відповідь викрадено з https://stackoverflow.com/users/73382/rob вище


1
А як щодо перевірки на сервері?
Т-моти

ModelState.Remove, правда? У будь-якому випадку, для мене проблема полягала в тому, що в мою модель було включено друге об'єднання ... первинний я хотів перевірити, але вторинному не потрібна перевірка на цій сторінці ... так, за цим сценарієм , потрібен був лише JQuery.
Mike_Matthews_II

Я думаю, що посилання розірвано, тому будь ласка, редагуйте Це часткова відповідь
T-moty

Дивно, що ви говорите, що це працює в MVC6 (у мене зараз немає можливості тестувати MVC6), але він не працює для MVC4, який я зараз використовую.
eaglei22

Питання до MVC / C # - не JS, відповідь не буде працювати на стороні сервера
mtbennett

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 Entity


1

AFAIK ви не можете видалити атрибут під час виконання, а лише змінити їх значення (тобто: readonly true / false) шукайте тут щось подібне . Як інший спосіб робити те, що ви хочете, не возившись з атрибутами, я піду за допомогою ViewModel для вашої конкретної дії, щоб ви могли вставити всю логіку, не порушуючи логіку, необхідну іншим контролерам. Якщо ви спробуєте отримати якийсь майстер (форма з декількома кроками), ви можете замість цього серіалізувати вже складені поля та разом із TempData перевести їх по ваших кроках. (для допомоги в серіалізації десеріалізації ви можете використовувати ф'ючерси на MVC )


1

Що сказав @Darin - це те, що я рекомендував би також. Однак я додам до цього (і у відповідь на один із коментарів), що ви також можете використовувати цей метод для примітивних типів, таких як біт, bool, навіть структури типу Guid, просто зробивши їх нульовими. Після цього 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

Я шукав рішення, де я можу використовувати ту саму модель для вставки та оновлення у веб-api. У моїй ситуації це завжди зміст тіла. Ці [Requiered]атрибути повинні бути пропущені , якщо це метод оновлення. У моєму рішенні ви розміщуєте атрибут [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 Attributte:

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

Якщо ви не хочете використовувати інший ViewModel, ви можете відключити перевірку клієнтів у представленні даних, а також видалити перевірки на сервері для тих властивостей, які ви хочете ігнорувати. Будь ласка, перевірте цю відповідь для більш глибокого пояснення https://stackoverflow.com/a/15248790/1128216


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

Так, Вимкнути необхідні атрибути можна. Створіть власний атрибут спеціального класу (зразок коду під назвою ChangeableRequired) в залежності від RequiredAtribute та додайте Включене властивість та замініть метод IsValid, щоб перевірити, чи він не скасований. Використовуйте відображення, щоб встановити вимкнену властивість, наприклад:

Спеціальний атрибут:

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;

Це відчуває себе приємно і чисто?

Примітка: Перевірка вище буде вимкнена, поки ваш об'єкт живий \ активний ...


Після кількох дебатів з цього приводу я хочу зазначити, що я не рекомендую відключати перевірку, а краще переглянути правило. Якщо ви відключите його, ви створите залежність, щоб повторно включити правило, і я б запропонував переглянути це правило.
Ернест Ганнінг

Зміна статичного значення, щоб повністю відключити всю перевірку цього поля, виконаного в рамках того самого процесу, звучить як жахлива ідея.
Джеремі Лакеман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.