Wyłącz wymagany atrybut walidacji w określonych okolicznościach


138

Zastanawiałem się, czy można wyłączyć atrybut Required validation w niektórych akcjach kontrolera. Zastanawiam się nad tym, ponieważ w jednym z moich formularzy edycji nie wymagam od użytkownika wprowadzania wartości pól, które już wcześniej określił. Jednak potem implementuję logikę, że kiedy wprowadzają wartość, używa specjalnej logiki do aktualizacji modelu, takiej jak haszowanie wartości itp.

Jakieś sugestie, jak obejść ten problem?

EDYCJA:
I tak, walidacja klienta jest tutaj problemem, ponieważ nie pozwoli im przesłać formularza bez wpisania wartości.


3
+1 dobry P. Dobrze byłoby wspomnieć tutaj o weryfikacji klienta. Jedną z opcji jest RequiredAttrcałkowite usunięcie i wykonanie sprawdzenia po stronie serwera, gdy zajdzie taka potrzeba. Ale to byłoby trudne dla klienta
gideon

4
Punkty dla każdego, kto obejmuje również wyłączenie niektórych pól z walidacji klienta (bez usuwania odniesień do walidacji jQuery)
gideon

Może brakuje mi twojego punktu widzenia, ale jeśli użytkownik już wcześniej określił wartości, to te wartości są już obecne, a zatem przejdą Wymaganą weryfikację. Miałeś na myśli coś innego?
Erik Funkenbusch

Ponieważ te wartości zostały od tamtego czasu zaszyfrowane, takie jak hasło i odpowiedź bezpieczeństwa, więc jeśli wprowadzą nową wartość w formularzu edycji, chcę ponownie zhaszować nową wartość przed wstawieniem, ale chcę również, aby opcja pozostała pusta coś w tym rodzaju.
Alex Hope O'Connor,

1
@gideon: zobacz odpowiedź Adriana Smitha: stackoverflow.com/a/9781066/114029
Leniel Maccaferri

Odpowiedzi:


76

Ten problem można łatwo rozwiązać za pomocą modeli widoków. Modele widoków to klasy, które są specjalnie dostosowane do potrzeb danego widoku. Na przykład w twoim przypadku możesz mieć następujące modele widoku:

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

    ... some other properties
}

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

    ... some other properties
}

które zostaną użyte w odpowiednich akcjach kontrolera:

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

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

Nie zawsze jest prawdziwe, jeśli masz wartość logiczną / bit, która nie ma wartości null. Ale tak naprawdę nie robi różnicy, ponieważ skończy się to prawda lub fałsz. Miałem css, aby podświetlić wymagane pola i fałszywie podświetlił element CheckBoxFor. Moje rozwiązanie: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
Robert Koch,

W aktualizacji mamy ogólnie (kolekcja FormCollection). czy mógłbyś wyjaśnić, w jaki sposób używasz modelu jako parametru
Raj Kumar,

4
Nie, zwykle nie mamy Update(FormCollection collection), a przynajmniej ja nigdy. I zawsze definiować i używać konkretnego widoku modelu: Update(UpdateViewView model).
Darin Dimitrov

Przeciążanie metod z tą samą akcją HTTP jest niedozwolone, więc wydaje mi się, że to nie zadziała. Czy coś mi brakuje?
e11s

@edgi, nie, niczego nie brakuje. To błąd w moim poście. Oczywiście należy wywołać drugą metodę akcji Insert. Dziękuję za zwrócenie uwagi.
Darin Dimitrov

55

Jeśli chcesz tylko wyłączyć sprawdzanie poprawności dla pojedynczego pola po stronie klienta, możesz zastąpić atrybuty walidacji w następujący sposób:

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

20
Co mi zadziałało przez jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
Robert Koch,

6
Podoba mi się to podejście, ale musiałem ponownie przeanalizować atrybuty walidacji formularza za pomocą: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ('formularz'). removeData ('walidator'); $ .validator.unobtrusive.parse ('selektor dla twojego formularza');
Yannick Smits

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- łatwiejsze do odczytania IMO.
eth0

Podobało mi się większość tego podejścia, aby zostawić mi dokładną kontrolę nad każdym polem, ale możesz dodać, aby anulować ModelState.IsValid podczas wysyłania danych do zapisania przez POST. Może powodować jakieś ryzyko?
Felipe FMMobile,

4
Jeśli chcesz go wyłączyć przez jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri

40

Wiem, że na to pytanie udzielono już dawno odpowiedzi i zaakceptowana odpowiedź faktycznie zadziała. Ale przeszkadza mi jedna rzecz: kopiowanie 2 modeli tylko w celu wyłączenia walidacji.

Oto moja sugestia:

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

W ten sposób nie musisz zawracać sobie głowy walidacją po stronie klienta / serwera, framework będzie zachowywał się tak, jak powinien. Ponadto, jeśli zdefiniujesz [Display]atrybut w klasie bazowej, nie musisz ponownie definiować go w pliku UpdateModel.

Nadal możesz używać tych klas w ten sam sposób:

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

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

Bardziej podoba mi się to podejście, zwłaszcza jeśli masz długą listę atrybutów walidacji. W swoim przykładzie możesz dodać więcej atrybutów w klasie bazowej, aby korzyści były bardziej widoczne. Jedyną wadą, jaką widzę, jest to, że nie ma możliwości dziedziczenia właściwości, aby zastąpić atrybuty. Na przykład, jeśli masz właściwość [Wymagane] w klasie bazowej, właściwość dziedzicząca również musi być [Wymagana], chyba że masz niestandardowy atrybut [Opcjonalny].
Yorro

Myślałem o czymś takim, chociaż mam model viewModel, który ma obiekt „Project”, który ma wiele atrybutów i chcę, aby tylko jeden z tych atrybutów był sprawdzany w określonych okolicznościach. Nie sądzę, że mogę po prostu zastąpić atrybut obiektu, prawda? jakakolwiek rada?
vincent de g

Nie możesz zastąpić atrybutu. Klasa bazowa powinna zawierać tylko atrybuty wspólne dla wszystkich podklas. Następnie twoje podklasy powinny definiować atrybuty, których potrzebują.
PhilDulac,

1
Najbardziej eleganckie, przejrzyste rozwiązanie wielokrotnego użytku. Replikacje są złe. Droga jest polimorfizmem. +1
T-moty

w moim przypadku klasa bazowa ma wymagany atrybut i chcę, aby nie była wymagana w mojej klasie nadrzędnej. Czy to możliwe bez dwóch kopii modelu?
Alexey Strakh

27

Możesz usunąć całą weryfikację z właściwości za pomocą następujących elementów w akcji kontrolera.

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

@ Komentarz Iana dotyczący MVC5

Nadal jest możliwe

ModelState.Remove("PropertyNameInModel");

Trochę denerwujące, że tracisz statyczne wpisywanie dzięki zaktualizowanemu interfejsowi API. Możesz osiągnąć coś podobnego do starego sposobu, tworząc instancję pomocnika HTML i używając metod NameExtensions .


Tyle że ... nie ma metody ModelStatepasującej do tego podpisu. Przynajmniej nie w MVC 5.
Ian Kemp

Nie chodziło o to, jak usunąć całą weryfikację. To jak usunąć wymaganą walidację pola. Możesz to zrobić, pozostawiając inne reguły walidacji.
Richard,

15

Strona klienta Aby wyłączyć walidację formularza, poniżej podano wiele opcji opartych na moich badaniach. Mam nadzieję, że jeden z nich będzie dla ciebie pracował.

opcja 1

Wolę to i to działa idealnie dla mnie.

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

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

i wzywając to jak

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

Opcja 2

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

Wariant 3

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

Opcja 4

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

Opcja 5

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

Po stronie serwera

Utwórz atrybut i oznacz swoją metodę akcji tym atrybutem. Dostosuj to, aby dostosować się do swoich konkretnych potrzeb.

[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();
        }
    }
}

Lepsze podejście zostało opisane tutaj. Włącz / wyłącz dynamicznie sprawdzanie poprawności po stronie serwera MVC


$ ('selektor wejścia'). each (function () {$ (this) .rules ('usuń');}); pomogło mi
Sachin Pakale

Pytanie dotyczyło w szczególności usunięcia wymaganej weryfikacji pól, a nie usunięcia całej weryfikacji. Twoja odpowiedź dotyczy usunięcia całej weryfikacji.
Richard,

14

Osobiście skłaniałbym się do podejścia, które Darin Dimitrov pokazał w swoim rozwiązaniu. Dzięki temu możesz korzystać z adnotacji danych z walidacją ORAZ mieć osobne atrybuty danych w każdym ViewModel odpowiadające wykonywanemu zadaniu. Aby zminimalizować ilość pracy związanej z kopiowaniem między modelem i modelem widoku, należy spojrzeć na AutoMapper lub ValueInjecter . Obie mają swoje mocne strony, więc sprawdź je obie.

Innym możliwym podejściem byłoby wyprowadzenie modelu widoku lub modelu z IValidatableObject. Daje to możliwość zaimplementowania funkcji Validate. W walidacji można zwrócić listę elementów ValidationResult lub wydać zwrot wydajności dla każdego problemu wykrytego podczas walidacji.

ValidationResult składa się z komunikatu o błędzie i listy ciągów z nazwami pól. Komunikaty o błędach zostaną wyświetlone w miejscu w pobliżu pól wprowadzania.

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

czy możemy to podpiąć po stronie klienta?
Muhammad Adeel Zahid

walidacja po stronie klienta jest zwykle wykonywana tylko dla pojedynczych pól, wykonując interaktywne informacje zwrotne dotyczące walidacji pola po polu, jako wygodę dla użytkownika. Ponieważ krok walidacji obiektu zazwyczaj obejmuje walidację zależną (wiele pól i / lub warunków, które są zewnętrzne w stosunku do samego obiektu), nie musi to koniecznie być wykonywane po stronie klienta, nawet jeśli można skompilować kod do JavaScript. Jeśli złożona / zależna walidacja po stronie klienta doda wartości w twoim przypadku, będziesz musiał użyć funkcji onsubmit-callback i zduplikować logikę walidacji po stronie klienta.
mindplay.dk

7

Uważam, że najczystszym sposobem jest wyłączenie weryfikacji po stronie klienta, a po stronie serwera będziesz musiał:

  1. ModelState ["SomeField"]. Errors.Clear (w kontrolerze lub utwórz filtr akcji, aby usunąć błędy przed wykonaniem kodu kontrolera)
  2. Dodaj ModelState.AddModelError z kodu kontrolera, gdy wykryjesz naruszenie wykrytych problemów.

Wydaje się, że nawet niestandardowy model widoku nie rozwiąże problemu, ponieważ liczba tych pól z wstępnie wypełnionymi odpowiedziami może się różnić. Jeśli tego nie zrobią, niestandardowy model widoku może być najłatwiejszym sposobem, ale używając powyższej techniki, możesz obejść problemy z walidacją.


1
Właśnie tego potrzebowałem. Miałem jeden widok i w tym widoku występują różne akcje, więc nie mogłem tego zrobić z różnymi ViewModels. To działa jak urok
ggderas

6

to była odpowiedź kogoś innego w komentarzach ... ale powinna to być odpowiedź prawdziwa:

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

testowane na MVC 6 z polem mającym [Required]atrybut

odpowiedź skradziona z https://stackoverflow.com/users/73382/rob powyżej


1
A co z walidacją po stronie serwera?
T-moty

ModelState.Remove, prawda? W każdym razie problem polegał na tym, że mój model zawierał drugą jednostkę ... podstawową, na której chciałem zweryfikować, ale druga nie wymagała walidacji na tej stronie ... więc w tym scenariuszu , tylko JQuery było potrzebne.
Mike_Matthews_II

Myślę, że link jest uszkodzony, więc edytuj. To jest częściowa odpowiedź
T-moty

To dziwne, że mówisz, że to działa w MVC6 (obecnie nie mam opcji testowania na MVC6), ale nie działa dla mnie na MVC4, którego obecnie używam.
eaglei

Pytanie dotyczy MVC / C # - nie JS, odpowiedź nie działałaby po stronie serwera
mtbennett

2

Miałem ten problem podczas tworzenia widoku edycji dla mojego modelu i chcę zaktualizować tylko jedno pole.

Moje rozwiązanie na najprostszy sposób polega na umieszczeniu dwóch pól za pomocą:

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

Komentarze to pole, które aktualizuję tylko w widoku edycji, które nie ma wymaganego atrybutu.

Jednostka ASP.NET MVC 3


1

AFAIK nie możesz usunąć atrybutu w czasie wykonywania, ale tylko zmienić ich wartości (np. Tylko do odczytu prawda / fałsz) szukaj tutaj czegoś podobnego . Jako inny sposób robienia tego, co chcesz, bez mieszania się z atrybutami, użyję ViewModel dla twojej konkretnej akcji, abyś mógł wstawić całą logikę bez łamania logiki potrzebnej innym kontrolerom. Jeśli spróbujesz uzyskać jakiś kreator (formularz wieloetapowy), możesz zamiast tego serializować już skompilowane pola i za pomocą TempData zabrać je ze sobą. (aby uzyskać pomoc w serializacji deserializacji, możesz użyć kontraktów futures MVC )


1

To, co powiedział @Darin, jest tym, co polecam. Dodam jednak do tego (i w odpowiedzi na jeden z komentarzy), że w rzeczywistości można również użyć tej metody dla typów pierwotnych, takich jak bit, bool, a nawet struktur, takich jak Guid, po prostu ustawiając je na wartość null. Gdy to zrobisz, Requiredatrybut będzie działał zgodnie z oczekiwaniami.

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

Od MVC 5 można to łatwo osiągnąć, dodając to do pliku global.asax.

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

1

Szukałem rozwiązania, w którym mógłbym użyć tego samego modelu do wstawiania i aktualizacji w webowym api. W mojej sytuacji jest to zawsze treść ciała. Te [Requiered]atrybuty muszą być pomijane, jeśli jest to metoda aktualizacji. W moim rozwiązaniu umieszczasz atrybut [IgnoreRequiredValidations]nad metodą. Jest to następujące:

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

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

    ...

Co jeszcze trzeba zrobić? Podczas uruchamiania należy utworzyć i dodać własny BodyModelValidator. To jest w HttpConfiguration i wygląda następująco: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());
        }

        ...

Mój własny BodyModelValidator pochodzi z DefaultBodyModelValidator. I doszedłem do wniosku, że musiałem zastąpić metodę „ShallowValidate”. W tym zastąpieniu filtruję wymagane walidatory modelu. A teraz klasa IgnoreRequiredOrDefaultBodyModelValidator i klasa atrybutów 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
    {

    }
}

Źródła:



0

W moim przypadku ten sam Model był używany na wielu stronach do celów ponownego wykorzystania. Stworzyłem więc atrybut niestandardowy, który sprawdza wykluczenia

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

        }
    }
}

i w kontrolerze

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

            //do something           

    }

Powiedz, że Model jest

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

Tak, można wyłączyć wymagany atrybut. Utwórz własny atrybut klasy niestandardowej (przykładowy kod o nazwie ChangeableRequired) do rozszerzenia z RequiredAtribute i Dodaj właściwość Disabled i Zastąp metodę IsValid, aby sprawdzić, czy jest wyłączona. Użyj odbicia, aby ustawić wyłączone poperty, na przykład:

Atrybut niestandardowy:

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

Zaktualizuj usługę, aby używać nowego atrybutu niestandardowego:

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

gdzie musisz wyłączyć właściwość, użyj odbicia, aby ją ustawić:

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;

Czy to przyjemne i czyste?

Uwaga: powyższa weryfikacja zostanie wyłączona, gdy instancja obiektu będzie żyła \ aktywna ...


Po kilku debatach na ten temat chciałbym wspomnieć, że nie polecam wyłączania walidacji, ale raczej przejrzyj regułę. Jeśli ją wyłączysz, utworzysz zależność, aby ponownie włączyć regułę i sugerowałbym przejrzenie reguły.
Ernest Gunning

Zmiana wartości statycznej, aby całkowicie wyłączyć całą weryfikację tego pola wykonywaną w ramach tego samego procesu, brzmi jak okropny pomysł.
Jeremy Lakeman,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.