Bagikan melalui


Validasi dalam Bahasa Domain-Specific

Sebagai penulis bahasa khusus domain (DSL), Anda dapat menentukan batasan validasi untuk memverifikasi bahwa model yang dibuat oleh pengguna bermakna. Misalnya, jika DSL Anda memungkinkan pengguna untuk menggambar pohon keluarga orang dan leluhur mereka, Anda dapat menulis batasan yang memastikan bahwa anak-anak memiliki tanggal lahir setelah orang tua mereka.

Anda dapat memiliki batasan validasi yang dijalankan saat model disimpan, saat dibuka, dan ketika pengguna secara eksplisit menjalankan perintah menu Validasi . Anda juga dapat menjalankan validasi di bawah kontrol program. Misalnya, Anda dapat menjalankan validasi sebagai respons terhadap perubahan nilai atau hubungan properti.

Validasi sangat penting jika Anda menulis templat teks atau alat lain yang memproses model pengguna Anda. Validasi memastikan bahwa model memenuhi prasyarat yang diasumsikan oleh alat tersebut.

Peringatan

Anda juga dapat mengizinkan batasan validasi ditentukan dalam ekstensi terpisah ke DSL Anda, bersama dengan perintah menu ekstensi dan penangan gerakan. Pengguna dapat memilih untuk menginstal ekstensi ini selain DSL Anda. Untuk informasi selengkapnya, lihat Memperluas DSL Anda dengan menggunakan MEF.

Menjalankan Validasi

Saat pengguna mengedit model, yaitu instans bahasa khusus domain Anda, tindakan berikut dapat menjalankan validasi:

  • Klik kanan diagram dan pilih Validasi Semua.

  • Klik kanan simpul atas di Explorer DSL Anda dan pilih Validasi Semua

  • Simpan model.

  • Buka model.

  • Selain itu, Anda dapat menulis kode program yang menjalankan validasi, misalnya, sebagai bagian dari perintah menu atau sebagai respons terhadap perubahan.

    Kesalahan validasi apa pun akan muncul di jendela Daftar Kesalahan . Pengguna dapat mengklik dua kali pesan kesalahan untuk memilih elemen model yang menjadi penyebab kesalahan.

Menentukan Batasan Validasi

Anda menentukan batasan validasi dengan menambahkan metode validasi ke kelas domain atau hubungan DSL Anda. Saat validasi dijalankan, baik oleh pengguna atau di bawah kontrol program, beberapa atau semua metode validasi dijalankan. Setiap metode diterapkan ke setiap instans kelasnya, dan mungkin ada beberapa metode validasi di setiap kelas.

Setiap metode validasi melaporkan kesalahan apa pun yang ditemukannya.

Nota

Metode validasi melaporkan kesalahan, tetapi tidak mengubah model. Jika Anda ingin menyesuaikan atau mencegah perubahan tertentu, lihat Alternatif untuk Validasi.

Untuk menentukan batasan validasi

  1. Aktifkan validasi di node Editor\Validation :

    1. Buka Dsl\DslDefinition.dsl.

    2. Di DSL Explorer, perluas simpul Editor dan pilih Validasi.

    3. Di jendela Properti, atur properti Penggunaan ke true. Sangat nyaman untuk mengatur semua properti ini.

    4. Klik Ubah Semua Templat di toolbar Penjelajah Solusi .

  2. Tulis definisi kelas parsial untuk satu atau beberapa kelas domain atau hubungan domain Anda. Tulis definisi ini dalam file kode baru di proyek Dsl .

  3. Awali setiap kelas dengan atribut ini:

    [ValidationState(ValidationState.Enabled)]
    
    • Secara default, atribut ini juga akan mengaktifkan validasi untuk kelas turunan. Jika Anda ingin menonaktifkan validasi untuk kelas turunan tertentu, Anda dapat menggunakan ValidationState.Disabled.
  4. Tambahkan metode validasi ke kelas. Setiap metode validasi dapat memiliki nama apa pun, tetapi memiliki satu parameter jenis ValidationContext.

    Ini harus diawali dengan satu atau beberapa ValidationMethod atribut:

    [ValidationMethod (ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ]
    

    ValidationCategories menentukan kapan metode dijalankan.

    Contohnya:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;

// Allow validation methods in this class:
[ValidationState(ValidationState.Enabled)]
// In this DSL, ParentsHaveChildren is a domain relationship
// from Person to Person:
public partial class ParentsHaveChildren
{
  // Identify the method as a validation method:
  [ValidationMethod
  ( // Specify which events cause the method to be invoked:
    ValidationCategories.Open // On file load.
  | ValidationCategories.Save // On save to file.
  | ValidationCategories.Menu // On user menu command.
  )]
  // This method is applied to each instance of the
  // type (and its subtypes) in a model:
  private void ValidateParentBirth(ValidationContext context)
  {
    // In this DSL, the role names of this relationship
    // are "Child" and "Parent":
     if (this.Child.BirthYear < this.Parent.BirthYear
        // Allow user to leave the year unset:
        && this.Child.BirthYear != 0)
      {
        context.LogError(
             // Description:
                       "Child must be born after Parent",
             // Unique code for this error:
                       "FAB001ParentBirthError",
              // Objects to select when user double-clicks error:
                       this.Child,
                       this.Parent);
    }
  }

Perhatikan poin-poin berikut tentang kode ini:

  • Anda dapat menambahkan metode validasi ke kelas domain atau hubungan domain. Kode untuk jenis ini ada di Dsl\Generated Code\Domain*.cs.

  • Setiap metode validasi diterapkan ke setiap instans kelasnya dan subkelasnya. Dalam kasus hubungan domain, setiap instans adalah tautan antara dua elemen model.

  • Metode validasi tidak diterapkan dalam urutan tertentu, dan setiap metode tidak diterapkan ke instans kelasnya dalam urutan yang dapat diprediksi.

  • Biasanya bukan praktik yang baik bagi suatu metode validasi untuk memperbarui konten penyimpanan, karena ini dapat menyebabkan hasil yang tidak konsisten. Sebagai gantinya, metode harus melaporkan kesalahan dengan memanggil context.LogError, LogWarning atau LogInfo.

  • Dalam panggilan LogError, Anda dapat memberikan daftar elemen model atau tautan hubungan yang akan dipilih saat pengguna mengklik dua kali pesan kesalahan.

  • Untuk informasi tentang cara membaca model dalam kode program, lihat Menavigasi dan Memperbarui Model dalam Kode Program.

    Contoh ini berlaku untuk model domain berikut. Hubungan ParentsHaveChildren memiliki peran yang diberi nama Anak dan Orang Tua.

    Diagram Definisi DSL - model pohon keluarga

Kategori Validasi

ValidationMethodAttribute Dalam atribut , Anda menentukan kapan metode validasi harus dijalankan.

Kategori Execution
ValidationCategories Saat pengguna memanggil perintah menu Validasi.
ValidationCategories Saat file model dibuka.
ValidationCategories Ketika file disimpan. Jika ada kesalahan validasi, pengguna akan diberi opsi untuk membatalkan operasi penyimpanan.
ValidationCategories Ketika file disimpan. Jika ada kesalahan dari metode dalam kategori ini, pengguna diperingatkan bahwa mungkin tidak mungkin untuk membuka kembali file.

Gunakan kategori ini untuk metode validasi yang menguji nama atau ID duplikat, atau kondisi lain yang dapat menyebabkan kesalahan pemuatan.
ValidationCategories Ketika metode ValidateCustom dipanggil. Validasi dalam kategori ini hanya dapat dipanggil dari kode program.

Untuk informasi selengkapnya, lihat Kategori Validasi Kustom.

Penempatan Metode Validasi

Anda sering dapat mencapai efek yang sama dengan menempatkan metode validasi pada jenis yang berbeda. Misalnya, Anda dapat menambahkan metode ke kelas Orang alih-alih hubungan ParentsHaveChildren, dan membuatnya iterasi melalui tautan:

[ValidationState(ValidationState.Enabled)]
public partial class Person
{[ValidationMethod
 ( ValidationCategories.Open
 | ValidationCategories.Save
 | ValidationCategories.Menu
 )
]
  private void ValidateParentBirth(ValidationContext context)
  {
    // Iterate through ParentHasChildren links:
    foreach (Person parent in this.Parents)
    {
        if (this.BirthYear <= parent.BirthYear)
        { ...

Menggabungkan batasan validasi. Untuk menerapkan validasi dalam urutan yang dapat diprediksi, tentukan metode validasi tunggal pada kelas pemilik, seperti elemen akar model Anda. Teknik ini juga memungkinkan Anda menggabungkan beberapa laporan kesalahan ke dalam satu pesan.

Kelemahannya adalah metode gabungan ini lebih sulit dikelola, dan semua batasan harus memiliki ValidationCategories. Oleh karena itu, kami sarankan Anda menyimpan setiap batasan dalam metode terpisah jika memungkinkan.

Meneruskan nilai dalam penyimpanan konteks. Parameter konteks memiliki kamus tempat Anda dapat menempatkan nilai arbitrer. Kamus berlanjut untuk masa pakai eksekusi validasi. Metode validasi tertentu dapat, misalnya, menyimpan jumlah kesalahan dalam konteks, dan menggunakannya untuk menghindari membanjiri jendela kesalahan dengan pesan berulang. Contohnya:

List<ParentsHaveChildren> erroneousLinks;
if (!context.TryGetCacheValue("erroneousLinks", out erroneousLinks))
erroneousLinks = new List<ParentsHaveChildren>();
erroneousLinks.Add(this);
context.SetCacheValue("erroneousLinks", erroneousLinks);
if (erroneousLinks.Count < 5) { context.LogError( ... ); }

Validasi Kelipatan

Metode validasi untuk memeriksa perkalian minimum secara otomatis dihasilkan untuk DSL Anda. Kode ditulis ke Dsl\Generated Code\MultiplicityValidation.cs. Metode ini berlaku saat Anda mengaktifkan validasi di simpul Editor\Validasi di DSL Explorer.

Jika Anda mengatur perkalian peran hubungan domain menjadi 1..* atau 1..1, tetapi pengguna tidak membuat tautan hubungan ini, pesan kesalahan validasi akan muncul.

Misalnya, jika DSL Anda memiliki kelas Person dan Town, serta hubungan PersonLivesInTown dengan hubungan 1..\* pada peran Town, maka untuk setiap Person yang tidak memiliki Town, pesan kesalahan akan ditampilkan.

Menjalankan Validasi dari Kode Program

Anda dapat menjalankan validasi dengan mengakses atau membuat ValidationController. Jika Anda ingin kesalahan ditampilkan kepada pengguna di jendela kesalahan, gunakan ValidationController yang dilampirkan ke DocData diagram Anda. Misalnya, jika Anda menulis perintah menu, CurrentDocData.ValidationController tersedia di kelas set perintah:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Modeling.Shell;
...
partial class MyLanguageCommandSet
{
  private void OnMenuMyContextMenuCommand(object sender, EventArgs e)
  {
   ValidationController controller = this.CurrentDocData.ValidationController;
...

Untuk informasi selengkapnya, lihat Cara: Menambahkan Perintah ke Menu Pintasan.

Anda juga dapat membuat pengontrol validasi terpisah, dan mengelola kesalahan sendiri. Contohnya:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Modeling.Shell;
...
Store store = ...;
VsValidationController validator = new VsValidationController(s);
// Validate all elements in the Store:
if (!validator.Validate(store, ValidationCategories.Save))
{
  // Deal with errors:
  foreach (ValidationMessage message in validator.ValidationMessages) { ... }
}

Menjalankan validasi saat perubahan terjadi

Jika Anda ingin memastikan bahwa pengguna segera diperingatkan jika model menjadi tidak valid, Anda dapat menentukan peristiwa penyimpanan yang menjalankan validasi. Untuk informasi selengkapnya tentang peristiwa penyimpanan, lihat Penanganan Aktivitas Menyebarkan Perubahan di Luar Model.

Selain kode validasi, tambahkan file kode kustom ke proyek DslPackage Anda, dengan konten yang mirip dengan contoh berikut. Kode ini menggunakan ValidationController yang dilampirkan ke dokumen. Pengontrol ini menampilkan kesalahan validasi dalam daftar kesalahan Visual Studio.

using System;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
namespace Company.FamilyTree
{
  partial class FamilyTreeDocData // Change name to your DocData.
  {
    // Register the store event handler:
    protected override void OnDocumentLoaded()
    {
      base.OnDocumentLoaded();
      DomainClassInfo observedLinkInfo = this.Store.DomainDataDirectory
         .FindDomainClass(typeof(ParentsHaveChildren));
      DomainClassInfo observedClassInfo = this.Store.DomainDataDirectory
         .FindDomainClass(typeof(Person));
      EventManagerDirectory events = this.Store.EventManagerDirectory;
      events.ElementAdded
         .Add(observedLinkInfo, new EventHandler<ElementAddedEventArgs>(ParentLinkAddedHandler));
      events.ElementDeleted.Add(observedLinkInfo, new EventHandler<ElementDeletedEventArgs>(ParentLinkDeletedHandler));
      events.ElementPropertyChanged.Add(observedClassInfo, new EventHandler<ElementPropertyChangedEventArgs>(BirthDateChangedHandler));
    }
    // Handler will be called after transaction that creates a link:
    private void ParentLinkAddedHandler(object sender,
                                ElementAddedEventArgs e)
    {
      this.ValidationController.Validate(e.ModelElement,
           ValidationCategories.Save);
    }
    // Called when a link is deleted:
    private void ParentLinkDeletedHandler(object sender,
                                ElementDeletedEventArgs e)
    {
      // Don't apply validation to a deleted item!
      // - Validate store to refresh the error list.
      this.ValidationController.Validate(this.Store,
           ValidationCategories.Save);
    }
    // Called when any property of a Person element changes:
    private void BirthDateChangedHandler(object sender,
                      ElementPropertyChangedEventArgs e)
    {
      Person person = e.ModelElement as Person;
      // Not interested in changes in other properties:
      if (e.DomainProperty.Id != Person.BirthYearDomainPropertyId)
          return;

      // Validate all parent links to and from the person:
      this.ValidationController.Validate(
        ParentsHaveChildren.GetLinksToParents(person)
        .Concat(ParentsHaveChildren.GetLinksToChildren(person))
        , ValidationCategories.Save);
    }
  }
}

Handler juga dipanggil setelah operasi Batalkan atau Ulangi yang memengaruhi tautan atau elemen.

Kategori Validasi Kustom

Selain kategori validasi standar, seperti Menu dan Buka, Anda dapat menentukan kategori Anda sendiri. Anda dapat memanggil kategori ini dari kode program. Pengguna tidak dapat memanggil mereka secara langsung.

Penggunaan umum untuk kategori kustom adalah untuk menentukan kategori yang menguji apakah model memenuhi prasyarat alat tertentu.

Untuk menambahkan metode validasi ke kategori tertentu, awali dengan atribut seperti ini:

[ValidationMethod(CustomCategory = "PreconditionsForGeneratePartsList")]
[ValidationMethod(ValidationCategory.Menu)]
private void TestForCircularLinks(ValidationContext context)
{...}

Nota

Anda dapat mengawali metode dengan atribut sebanyak [ValidationMethod()] yang Anda inginkan. Anda dapat menambahkan metode ke kategori kustom dan standar.

Untuk memanggil validasi kustom:


// Invoke all validation methods in a custom category:
validationController.ValidateCustom
  (store, // or a list of model elements
   "PreconditionsForGeneratePartsList");

Alternatif untuk Validasi

Batasan validasi melaporkan kesalahan, tetapi jangan mengubah model. Jika, sebaliknya, Anda ingin mencegah model menjadi tidak valid, Anda dapat menggunakan teknik lain.

Namun, teknik ini tidak disarankan. Biasanya lebih baik membiarkan pengguna memutuskan cara memperbaiki model yang tidak valid.

Sesuaikan perubahan untuk memulihkan model ke validitas. Misalnya, jika pengguna menetapkan properti di atas maksimum yang diizinkan, Anda dapat mengatur ulang properti ke nilai maksimum. Untuk melakukan ini, tentukan aturan. Untuk informasi selengkapnya, lihat Aturan Menyebarkan Perubahan Dalam Model.

Gulung balik transaksi jika ada upaya perubahan yang tidak valid. Anda juga dapat menentukan aturan untuk tujuan ini, tetapi dalam beberapa kasus dimungkinkan untuk mengambil alih penangan properti OnValueChanging(), atau untuk mengambil alih metode seperti OnDeleted(). Untuk mengembalikan transaksi, gunakan this.Store.TransactionManager.CurrentTransaction.Rollback(). Untuk informasi selengkapnya, lihat Penangan Perubahan Nilai Properti Domain.

Peringatan

Pastikan bahwa pengguna tahu bahwa perubahan telah disesuaikan atau digulung balik. Misalnya, gunakan System.Windows.Forms.MessageBox.Show("message").