ASP.NET MVC validasi bersyarat

129

Bagaimana cara menggunakan anotasi data untuk melakukan validasi bersyarat pada model?

Misalnya, katakanlah kita memiliki model berikut (Orang dan Senior):

public class Person
{
    [Required(ErrorMessage = "*")]
    public string Name
    {
        get;
        set;
    }

    public bool IsSenior
    {
        get;
        set;
    }

    public Senior Senior
    {
        get;
        set;
    }
}

public class Senior
{
    [Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
    public string Description
    {
        get;
        set;
    }
}

Dan tampilan berikut:

<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>

<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>

<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>

Saya ingin menjadi bidang persyaratan bersyarat properti "Senior.Description" berdasarkan pemilihan kelayakan "IsSenior" (benar -> wajib). Bagaimana cara menerapkan validasi bersyarat dalam ASP.NET MVC 2 dengan anotasi data?

Peter Stegnar
sumber
1
Saya baru-baru ini mengajukan pertanyaan serupa: stackoverflow.com/questions/2280539/…
Darin Dimitrov
Saya bingung. Sebuah Seniorobjek selalu senior, jadi mengapa IsSenior dapat palsu dalam kasus itu. Jangan Anda hanya perlu properti 'Person.Senior' menjadi nol ketika Person.IsSeniorsalah. Atau mengapa tidak menerapkan IsSeniorproperti sebagai berikut: bool IsSenior { get { return this.Senior != null; } }.
Steven
Steven: "IsSenior" diterjemahkan ke bidang kotak centang di tampilan. Ketika pengguna memeriksa kotak centang "IsSenior" maka bidang "Senior.Description" menjadi wajib.
Peter Stegnar
Darin Dimitrov: Ya sudah, tapi tidak cukup. Anda lihat, bagaimana Anda akan mencapai bahwa pesan kesalahan ditambahkan ke bidang tertentu? Jika Anda memvalidasi di tingkat objek, Anda mendapatkan kesalahan di tingkat objek. Saya perlu kesalahan di tingkat properti.
Peter Stegnar

Jawaban:

150

Ada cara yang jauh lebih baik untuk menambahkan aturan validasi bersyarat di MVC3; minta model Anda mewarisi IValidatableObjectdan mengimplementasikan Validatemetode:

public class Person : IValidatableObject
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
        if (IsSenior && string.IsNullOrEmpty(Senior.Description)) 
            yield return new ValidationResult("Description must be supplied.");
    }
}

Baca lebih lanjut di Memperkenalkan ASP.NET MVC 3 (Pratinjau 1) .

viperguynaz
sumber
jika properti adalah tipe "int", yang membutuhkan nilai, jika mengisi bidang itu, Validasi tidak berfungsi ..
Jeyhun Rahimov
2
Sayangnya, Microsoft meletakkan ini di lapisan yang salah - validasi adalah logika bisnis dan antarmuka ini di DLL System.Web Untuk menggunakan ini, Anda harus memberi lapisan bisnis Anda ketergantungan pada teknologi presentasi.
NightOwl888
7
Anda lakukan jika Anda menerapkannya - lihat contoh lengkap di falconwebtech.com/post/…
viperguynaz
4
falconwebtech.com/post/… - @viperguynaz ini tidak berfungsi
Smit Patel
1
@RayLoveless Anda harus menelepon ModelState.IsValid- tidak menelepon Validasi langsung
viperguynaz
63

Saya telah memecahkan ini dengan menangani kamus "ModelState" , yang berisi oleh controller. Kamus ModelState mencakup semua anggota yang harus divalidasi.

Ini solusinya:

Jika Anda perlu menerapkan validasi bersyarat berdasarkan pada beberapa bidang (mis. Jika A = true, maka B diperlukan), sambil mempertahankan pesan kesalahan tingkat properti (ini tidak berlaku untuk validator kustom yang ada di level objek) Anda dapat mencapai ini dengan menangani "ModelState", dengan hanya menghapus validasi yang tidak diinginkan darinya.

... Di beberapa kelas ...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

[Required(ErrorMessage = "*")] 
public string DepentedProperty
{
  get;
  set;
}

... kelas berlanjut ...

... Dalam beberapa aksi pengontrol ...

if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
   this.ModelState.Remove("DepentedProperty");
}

...

Dengan ini kami mencapai validasi bersyarat, sambil membiarkan semuanya tetap sama.


MEMPERBARUI:

Ini adalah implementasi terakhir saya: Saya telah menggunakan antarmuka pada model dan atribut tindakan yang memvalidasi model yang mengimplementasikan antarmuka tersebut. Antarmuka mengatur metode Validasi (ModelStateDictionary modelState). Atribut pada tindakan hanya memanggil Validasi (modelState) pada IValidatorSomething.

Saya tidak ingin mempersulit jawaban ini, jadi saya tidak menyebutkan detail implementasi akhir (yang, pada akhirnya, masalah dalam kode produksi).

Peter Stegnar
sumber
17
Kelemahannya adalah bahwa salah satu bagian logika validasi Anda terletak di model dan bagian lain di controller.
Kristof Claes
Ya tentu saja ini tidak perlu. Saya hanya menunjukkan contoh paling dasar. Saya telah menerapkan ini dengan antarmuka pada model dan dengan atribut tindakan yang memvalidasi model yang mengimplementasikan antarmuka yang disebutkan. Antarmuka menampilkan metode Validasi (ModelStateDictionary modelState). Jadi akhirnya Anda MELAKUKAN semua validasi dalam model. Pokoknya bagus.
Peter Stegnar
Saya suka kesederhanaan dari pendekatan ini dalam waktu yang berarti sampai tim MVC membangun sesuatu yang lebih baik di luar kotak. Tetapi apakah solusi Anda berfungsi dengan validasi sisi klien diaktifkan ??
Aaron
2
@ Harun: Saya senang Anda menyukai solusi, tetapi sayangnya solusi ini tidak berfungsi dengan validasi sisi klien (karena setiap atribut validasi memerlukan implementasi JavaScript-nya). Anda dapat membantu diri sendiri dengan atribut "Remote", jadi hanya panggilan Ajax yang akan dipancarkan untuk memvalidasinya.
Peter Stegnar
Apakah Anda dapat memperluas jawaban ini? Ini masuk akal, tetapi saya ingin memastikan saya yakin. Saya dihadapkan dengan situasi yang tepat ini, dan saya ingin menyelesaikannya.
Richard B
36

Saya memiliki masalah yang sama kemarin tetapi saya melakukannya dengan cara yang sangat bersih yang berfungsi untuk validasi sisi klien dan sisi server.

Kondisi: Berdasarkan nilai properti lain dalam model, Anda ingin membuat properti lain diperlukan. Ini kodenya

public class RequiredIfAttribute : RequiredAttribute
{
    private String PropertyName { get; set; }
    private Object DesiredValue { get; set; }

    public RequiredIfAttribute(String propertyName, Object desiredvalue)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (proprtyvalue.ToString() == DesiredValue.ToString())
        {
            ValidationResult result = base.IsValid(value, context);
            return result;
        }
        return ValidationResult.Success;
    }
}

Di sini PropertyName adalah properti yang Anda inginkan untuk membuat kondisi Anda DesiredValue adalah nilai khusus dari PropertyName (properti) yang harus divalidasi oleh properti Anda yang lain.

Katakanlah Anda memiliki yang berikut ini

public class User
{
    public UserType UserType { get; set; }

    [RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
    public string Password
    {
        get;
        set;
    }
}

Akhirnya tetapi bukan yang terakhir, daftarkan adaptor untuk atribut Anda sehingga dapat melakukan validasi sisi klien (saya taruh di global.asax, Application_Start)

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
Dan Hunex
sumber
Ini adalah titik awal asli miroprocessordev.blogspot.com/2012/08/...
Dan Hunex
Apakah ada solusi equvalent di asp.net mvc2? Kelas ValidationResult, ValidationContext tidak tersedia di asp.net mvc2 (.net framework 3.5)
User_MVC
2
Ini hanya berfungsi di sisi server seperti yang dinyatakan oleh blog tertaut
Pakman
2
Saya berhasil mendapatkan ini bekerja di sisi klien dengan MVC5, tetapi di klien itu menjalankan validasi tidak peduli apa DesiredValue.
Geethanga
1
@Dan Hunex: Di MVC4, saya belum berhasil bekerja dengan baik di sisi klien dan menjalankan validasi apa pun DesiredValue. Ada bantuan, tolong?
Jack
34

Saya telah menggunakan nuget menakjubkan ini yang melakukan anotasi dinamis ExpressiveAnnotations

Anda dapat memvalidasi logika apa pun yang dapat Anda impikan:

public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
Korayem
sumber
3
Pustaka ExpressiveAnnotation adalah solusi paling fleksibel dan umum dari semua jawaban di sini. Terima kasih telah berbagi!
Sudhanshu Mishra
2
Saya telah membenturkan kepala saya mencoba mencari solusi untuk hari yang solid. Catatan Ekspresif tampaknya menjadi perbaikan bagi saya!
Caverman
Pustaka ExpressiveAnnotation mengagumkan!
Doug Knudsen
1
Ini memiliki dukungan sisi klien juga!
Nattrass
1
Tidak ada dukungan untuk .NET Core, dan sepertinya itu tidak akan terjadi.
gosr
18

Anda dapat menonaktifkan validator secara kondisional dengan menghapus kesalahan dari ModelState:

ModelState["DependentProperty"].Errors.Clear();
Pavel Chuchuva
sumber
6

Sekarang ada kerangka kerja yang melakukan validasi bersyarat ini (di antara validasi anotasi data berguna lainnya) di luar kotak: http://foolproof.codeplex.com/

Secara khusus, lihat validator [RequiredIfTrue ("IsSenior")]. Anda meletakkannya langsung di properti yang ingin divalidasi, sehingga Anda mendapatkan perilaku kesalahan validasi yang diinginkan terkait dengan properti "Senior".

Ini tersedia sebagai paket NuGet.

bojingo
sumber
3

Anda perlu memvalidasi pada tingkat Orang, bukan pada tingkat Senior, atau Senior harus memiliki referensi ke Orang induknya. Menurut saya, Anda memerlukan mekanisme validasi diri yang mendefinisikan validasi pada Person dan bukan pada salah satu propertinya. Saya tidak yakin, tapi saya pikir DataAnnotations tidak mendukung hal ini. Apa yang dapat Anda lakukan buat milik Anda Attributeyang berasal dari ValidationAttributeyang dapat didekorasi di tingkat kelas dan selanjutnya buat validator khusus yang juga memungkinkan validator tingkat-kelas itu untuk dijalankan.

Saya tahu Blok Aplikasi Validasi mendukung validasi diri di luar kotak, tetapi VAB memiliki kurva belajar yang cukup curam. Namun demikian, inilah contoh menggunakan VAB:

[HasSelfValidation]
public class Person
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    [SelfValidation]
    public void ValidateRange(ValidationResults results)
    {
        if (this.IsSenior && this.Senior != null && 
            string.IsNullOrEmpty(this.Senior.Description))
        {
            results.AddResult(new ValidationResult(
                "A senior description is required", 
                this, "", "", null));
        }
    }
}
Steven
sumber
"Anda perlu memvalidasi pada tingkat Orang, bukan pada tingkat Senior" Ya ini adalah pilihan, tetapi Anda kehilangan kemampuan bahwa kesalahan ditambahkan ke bidang tertentu, yang diperlukan dalam objek Senior.
Peter Stegnar
3

Saya memiliki masalah yang sama, memerlukan modifikasi atribut [Wajib] - membuat bidang diperlukan dalam ketergantungan http request. Solusinya mirip dengan jawaban Dan Hunex, tetapi solusinya tidak bekerja dengan benar (lihat komentar). Saya tidak menggunakan validasi yang tidak mencolok, cukup MicrosoftMvcValidation.js di luar kotak. Ini dia. Terapkan atribut khusus Anda:

public class RequiredIfAttribute : RequiredAttribute
{

    public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
    {

    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {

    //You can put your logic here   

        return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
    }


}

Maka Anda perlu mengimplementasikan penyedia kustom Anda untuk menggunakannya sebagai adaptor di global.asax Anda

public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{

    ControllerContext ccontext;
    public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
       : base(metadata, context, attribute)
    {
        ccontext = context;// I need only http request
    }

//override it for custom client-side validation 
     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
     {       
               //here you can customize it as you want
         ModelClientValidationRule rule = new ModelClientValidationRule()
         {
             ErrorMessage = ErrorMessage,
    //and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"    
             ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
         };
         return new ModelClientValidationRule[] { rule };
      }
}

Dan modifikasi global.asax Anda dengan sebuah garis

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));

dan ini dia

[RequiredIf]
public string NomenclatureId { get; set; }

Keuntungan utama bagi saya adalah bahwa saya tidak perlu kode validator klien kustom seperti dalam kasus validasi yang tidak mencolok. ini berfungsi seperti [Diperlukan], tetapi hanya dalam kasus yang Anda inginkan.

Sarang
sumber
Bagian tentang perluasan DataAnnotationsModelValidatoradalah apa yang perlu saya lihat. Terima kasih.
twip
2

Lihatlah Pengesahan Bersyarat Simon Ince di MVC .

Saya sedang mengerjakan proyek contohnya sekarang.

Merritt
sumber
0

Penggunaan umum untuk penghapusan kesalahan kondisional dari Model State:

  1. Buat bagian pertama kondisional dari tindakan pengontrol
  2. Lakukan logika untuk menghapus kesalahan dari ModelState
  3. Lakukan sisa dari logika yang ada (biasanya validasi Model Model, lalu yang lainnya)

Contoh:

public ActionResult MyAction(MyViewModel vm)
{
    // perform conditional test
    // if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")

    // Do typical model state validation, inside following if:
    //     if (!ModelState.IsValid)

    // Do rest of logic (e.g. fetching, saving

Dalam contoh Anda, pertahankan semuanya apa adanya dan tambahkan logika yang disarankan untuk Tindakan Pengendali Anda. Saya mengasumsikan ViewModel Anda yang diteruskan ke action controller memiliki objek Person dan Senior Person dengan data yang terisi di dalamnya dari UI.

Jeremy Ray Brown
sumber
0

Saya menggunakan MVC 5 tetapi Anda dapat mencoba sesuatu seperti ini:

public DateTime JobStart { get; set; }

[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }

Dalam kasus Anda, Anda akan mengatakan sesuatu seperti "IsSenior == true". Maka Anda hanya perlu memeriksa validasi pada tindakan posting Anda.


sumber