Отключить обязательный атрибут проверки при определенных обстоятельствах

138

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

Есть предложения о том, как обойти эту проблему?

РЕДАКТИРОВАТЬ:
И да, проверка клиента - это проблема, поскольку она не позволит им отправить форму без ввода значения.

Алекс Хоуп О'Коннор
источник
3
+1 хорошо В. Было бы хорошо упомянуть здесь проверку клиента. Один из вариантов - RequiredAttrполностью удалить его и при необходимости выполнить проверку на стороне сервера . Но это будет сложно для клиента
гидеон
4
Очки для всех, кто также касается отключения определенных полей от проверки клиента (без удаления ссылок на проверку jquery)
гидеон
Возможно, я упустил вашу точку зрения, но если пользователь уже указал значения заранее, то эти значения уже присутствуют и, таким образом, пройдут обязательную проверку. Вы что-то еще имели в виду?
Эрик Фанкенбуш
Поскольку эти значения с тех пор были хешированы, например пароль и ответ безопасности, поэтому, если они вводят новое значение в форме редактирования, я хочу повторно хешировать новое значение перед вставкой, но я также хочу, чтобы параметр для него оставался пустым Такие вещи.
Alex Hope O'Connor
1
@gideon: см. ответ Адриана Смита: stackoverflow.com/a/9781066/114029
Лениэль Маккаферри

Ответы:

78

Эту проблему легко решить, используя модели представления. Модели представления - это классы, специально адаптированные к потребностям данного представления. Так, например, в вашем случае у вас могут быть следующие модели представления:

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. Но на самом деле это не имеет значения, так как в конечном итоге это будет истина или ложь. У меня был css для выделения необходимых полей, и он ложно выделил элемент CheckBoxFor. Мое решение: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
Роберт Кох
В обновлении у нас обычно есть (коллекция FormCollection). не могли бы вы объяснить, как вы используете модель в качестве параметра
Радж Кумар
4
Нет, обычно у нас нет Update(FormCollection collection), по крайней мере, никогда. Я всегда определить и использовать конкретную модель представления: Update(UpdateViewView model).
Дарин Димитров
Методы перегрузки с одним и тем же действием HTTP недопустимы, поэтому мне кажется, что это не сработает. Я что-то упускаю?
e11s 03
@edgi, нет, ты ничего не упускаешь. Это ошибка в моем посте. Очевидно, следует вызвать второй метод действия Insert. Спасибо за указание на это.
Дарин Димитров
55

Если вы просто хотите отключить проверку для одного поля на стороне клиента, вы можете переопределить атрибуты проверки следующим образом:

@Html.TextBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})
Адриан Смит
источник
21
Что у меня сработало через jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
Роберт Кох
7
Мне нравится этот подход, но мне нужно было повторно проанализировать атрибуты проверки формы, используя: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ('форма'). removeData ('валидатор'); $ .validator.unobtrusive.parse ('селектор для вашей формы');
Янник Смитс
15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- легче читать ИМО.
eth0
Мне больше всего понравился этот подход, позволяющий мне точно контролировать каждое поле, но вы можете добавить, чтобы отменить ModelState.IsValid при отправке данных для сохранения с помощью POST. Может вызвать какой-то риск?
Felipe FMMobile
4
Если вы хотите отключить его через jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri
41

Я знаю, что на этот вопрос давным-давно был дан ответ, и принятый ответ действительно сработает. Но есть одна вещь, которая меня беспокоит: необходимость копировать 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)
{
    ...
}
ФилДулак
источник
Мне больше нравится этот подход, особенно если у вас есть длинный список атрибутов проверки. В вашем примере вы можете добавить больше атрибутов в базовый класс, чтобы преимущество было более очевидным. Единственный недостаток, который я вижу, заключается в том, что нет способа наследования свойств для переопределения атрибутов. Например, если у вас есть свойство [Required] в базовом классе, наследуемое свойство также будет иметь значение [Required], если у вас нет настраиваемого атрибута [Optional].
Йорро
Я тоже подумал о чем-то подобном, хотя у меня есть viewModel, у которого есть объект Project, который имеет несколько атрибутов, и я хочу, чтобы только один из этих атрибутов подтверждался при определенных обстоятельствах. Я не думаю, что могу просто переопределить атрибут объекта, верно? любой совет?
vincent de g
Вы не можете переопределить атрибут. Базовый класс должен содержать только общие атрибуты для всех подклассов. Затем ваши подклассы должны определить необходимые им атрибуты.
PhilDulac
1
Самое элегантное, многоразовое и понятное решение. Репликации плохие. Полиморфизм - это путь. +1
T-moty
в моем случае базовый класс имеет обязательный атрибут, и я хочу, чтобы он не требовался в моем родительском классе. Возможно ли без двух экземпляров модели?
Алексей Страх
29

Вы можете удалить все проверки свойства с помощью следующих действий в вашем контроллере.

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

Комментарий @ Ian относительно MVC5

Еще возможно следующее

ModelState.Remove("PropertyNameInModel");

Немного раздражает, что вы теряете статическую типизацию с обновленным API. Вы можете добиться чего-то похожего на старый способ, создав экземпляр помощника HTML и используя методы NameExtensions .

jps
источник
Только вот ... нет метода, ModelStateкоторый бы соответствовал этой подписи. По крайней мере, не в MVC 5.
Ян Кемп,
Вопрос не в том, как удалить всю валидацию. Это было как убрать обязательную проверку поля. Возможно, вы захотите сделать это, оставив другие правила проверки на месте.
Ричард
Это действительно лучшее решение, и то, что у меня сработало даже в ядре .net, спасибо @jps ModelState.Remove ("PropertyNameInModel");
rogue39nin
17

Клиентская сторона Для отключения проверки формы ниже приведены несколько вариантов, основанных на моем исследовании. Надеюсь, один из них подойдет вам.

Опция 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 динамически.

int-я
источник
$ ('селектор ввода'). each (function () {$ (this) .rules ('remove');}); помог мне
Сачин Пакале
Вопрос был конкретно об удалении обязательной проверки поля, а не об удалении всей проверки. Ваш ответ касается удаления всех проверок.
Ричард
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;
}
nttakr
источник
мы можем подключить это на стороне клиента?
Мухаммад Адил Захид,
Проверка на стороне клиента обычно выполняется только для отдельных полей, выполняя интерактивную обратную связь по проверке поля за полем для удобства пользователя. Поскольку этап проверки объекта обычно включает в себя зависимую проверку (несколько полей и / или условий, которые являются внешними по отношению к самому объекту), это не обязательно может быть выполнено на стороне клиента, даже если вы можете скомпилировать код на JavaScript. Если сложная / зависимая проверка на стороне клиента действительно добавляет ценность в вашем случае, вам нужно будет использовать onsubmit-callback и продублировать логику проверки на стороне клиента.
mindplay.dk
8

Я считаю, что самый чистый способ здесь отключить проверку на стороне клиента, и на стороне сервера вам нужно будет:

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

Кажется, что даже настраиваемая модель представления здесь не решит проблему, потому что количество полей с предварительным ответом может варьироваться. Если это не так, то пользовательская модель представления действительно может быть самым простым способом, но с помощью описанной выше техники вы можете обойти свои проблемы с проверкой.

Адам Тюлипер - MSFT
источник
1
Это как раз то, что мне нужно. У меня было одно представление, и в нем выполняются разные действия, поэтому я не мог сделать это с разными моделями представления. Это работает как шарм
ggderas
6

это был чей-то ответ в комментариях ... но это должен быть настоящий ответ:

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

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

ответ украден с https://stackoverflow.com/users/73382/rob выше

Майк Мэттьюз II
источник
1
А как насчет проверки на стороне сервера?
T-moty
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

Фелипе FMMobile
источник
1

AFAIK вы не можете удалить атрибут во время выполнения, а только изменить их значения (например, только для чтения, true / false), посмотрите здесь что-то подобное . В качестве еще одного способа делать то, что вы хотите, не вмешиваясь в атрибуты, я выберу ViewModel для вашего конкретного действия, чтобы вы могли вставить всю логику, не нарушая логику, необходимую для других контроллеров. Если вы попытаетесь получить какой-то мастер (многоступенчатую форму), вы можете вместо этого сериализовать уже скомпилированные поля и с помощью TempData переносить их по своим шагам. (для помощи в сериализации десериализации вы можете использовать фьючерсы MVC )

Иридио
источник
1

То, что сказал @Darin, я тоже рекомендую. Однако я бы добавил к нему (и в ответ на один из комментариев), что вы фактически также можете использовать этот метод для примитивных типов, таких как bit, bool, даже структур, таких как 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

Я искал решение, в котором я мог бы использовать ту же модель для вставки и обновления в веб-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:

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, добавьте свойство Disabled и переопределите метод 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;

Это приятно и чисто?

NB: приведенная выше проверка будет отключена, пока ваш экземпляр объекта жив \ активен ...

Эрнест Ганнинг
источник
После нескольких дебатов по этому поводу я хотел бы упомянуть, что я не рекомендую отключать проверку, а лучше пересмотрю правило. Если вы отключите его, вы создадите зависимость для повторного включения правила, и я предлагаю просмотреть это правило.
Эрнест Ганнинг
Изменение статического значения для полного отключения всех проверок этого поля, выполняемых в том же процессе, звучит как ужасная идея.
Джереми Лейкман