C# cơ bản .NET Core
Lập trình C# (C Sharp)
Type (Bài trước)

Khái niệm về Annotation (Attribute)

Một thuộc tính chú thích (Annotation / Attribute) tác động vào một thành phần nào đó của chương trình (lớp, phương thức, thuộc tính) nó là một phần của siêu dữ liệu (metadata - loại dữ liệu cung cấp thêm thông tin về đối tượng nào đó). Annotation giúp thêm thông tin vào lớp, phương thức, thuộc tính những đoạn code mở rộng. Tính năng này trong Java gọi là Annotation, trong C# gọi là Attribute.

Các thuộc tính chú thích có thể được truy xuất tra cứu ở thời điểm thực thi bằng kỹ thuật gọi là reflection, truy xuất ngược từ đối tượng biết được nguồn gốc mà đối tượng đó sinh ra (lớp).

Trong C# có định nghĩa sẵn vô số các Attribute, để sử dụng nó bạn chỉ cần viết tên Attribute trong dấu [] trước đối tượng áp dụng như lớp, phương thức, thuộc tính lớp (có tham số như hàm, nếu Attribute đó yêu cầu).

[AttributeName(param1, param2 ...)]

Ví dụ: trong C# có thuộc tính Obsolete, để đánh dấu một phương thức, lớp ... nào đó là lạc hậu. Có nghĩa là thêm thông tin cho trình biên dịch biết một thành phần nào đó là đã lạc hậu, ví dụ:

public class MyClass {

    [Obsolete ("Phương thức này lỗi thời, hãy  dùng phương thức Abc")]
        public static void Method1 () {
            Console.WriteLine ("Phương thức chạy");
        }
}

Đã đánh dấu phương thức Method1 là lạc hậu, nếu còn sử dụng sẽ được trình biên dịch cảnh báo.

Kết quả là khi soạn code, hay khi biên dịch sẽ có cảnh báo.

Attribute không chỉ là thêm thông tin sử dụng bởi trình biên dịch, mà nó thực sự thêm các đặc tính mà code sẽ đọc được ở thời điểm thực thi.

Tự tạo Attribute C#

Để tạo Attribute riêng khá đơn giản, bạn chỉ việc tạo một lớp kế thừ từ System.Attribute. Tuy nhiên để sử dụng được trước tiên cần thành thạo kỹ thuật Reflection trong C#. Ở mức độ đơn giản, hãy tìm hiểu và sử dụng Type tại Đọc thuộc tính lớp với Type . Giờ thử thực hành tạo một Attribute riêng đặt tên là MotaAttribute, thuộc tính này để bạn thêm thông tin là một chuỗi (string) mô tả nào đó cho thành phần áp dụng (như lớp, thuộc tính, phương thức)

MotaAttribute.cs
using System;

namespace CS026_Attribute {

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method)]
    public class MotaAttribute : Attribute // có thể đặt tên Mota thay cho MotaAttribute
    {
        // Phương thức khởi tạo
        public MotaAttribute(string v) => Description = v;

        public string Description {set; get;}
    }

}

Trong định nghĩa trên, [AttributeUsage( ... )] là để thiết lập thuộc tính cho Mota, cho biết thuộc tính này áp dụng được cho những thành phần nào, như ví dụ trênn: nghĩa là được phép áp dụng cho các thuộc tính lớp.

  • AttributeTargets.Property có nghĩa áp dụng được cho thuộc tính của lớp
  • AttributeTargets.Class có nghĩa áp dụng được cho lớp
  • AttributeTargets.Method thì áp dụng được cho phương thức

Như vậy bạn đã tạo được một thuộc tính Mota, áp dụng cho đối tượng nào thì chỉ cần viết

[Mota("mô tả gì đó")]

Thì đối tượng đó có thêm thông tin Description với nội dung do bạn thêm vào.s

Áp dụng Attribute Mota

User.cs

using System;

namespace CS026_Attribute {

    [Mota("Lớp biểu diễn người dùng")]                  // thêm Attribute cho lớp
    public class User
    {
        [Mota("Thuộc tính lưu tuổi")]                   // thêm Attribute cho thuộc tính lớp
        public int age {set; get;}

        [Mota("Phương thức này hiện thị age")]          // thêm Attribute cho phương thức
        public void ShowAge() {}
    }

}

Thông tin thêm vào bởi Attribute có thể đọc được bằng kỹ thuật Reflection (đọc thêm và Type - Reflect)

Để đọc Attribute sử dụng Type trên đối tượng với phương thức GetCustomAttributes

using System;

namespace CS026_Attribute {
  class TestReadAttribute {
    public static void test () {
      var a = new User ();

      // Đọc các Attribute của lớp
      foreach (Attribute attr in a.GetType ().GetCustomAttributes (false)) {
        MotaAttribute mota = attr as MotaAttribute;
        if (mota != null) {
          Console.WriteLine ($"{a.GetType().Name,10} : {mota.Description}");
        }
      }

      // Đọc Attribute của từng thuộc tính lớp
      foreach (var thuoctinh in a.GetType ().GetProperties ()) {
        foreach (Attribute attr in thuoctinh.GetCustomAttributes (false)) {
          MotaAttribute mota = attr as MotaAttribute;
          if (mota != null) {
            Console.WriteLine ($"{thuoctinh.Name,10} : {mota.Description}");
          }
        }
      }

      // Đọc Attribute của phương thức
      foreach (var m in a.GetType ().GetMethods ()) {
        foreach (Attribute attr in m.GetCustomAttributes (false)) {
          MotaAttribute mota = attr as MotaAttribute;
          if (mota != null) {
            Console.WriteLine ($"{m.Name,10} : {mota.Description}");
          }
        }
      }



    }

  }
}

Chạy, đọc được thông tin:

      User : Lớp biểu diễn người dùng
       age : Thuộc tính lưu tuổi
   ShowAge : Phương thức này hiện thị age

Chú ý, các phương thức GetCustomAttributes() của Reflection sử dụng ở trên lấy Attribute được thêm vào. Chạy code trên, kết quả:

Data Annotation/Attribute trong C#

Các Data Annotation/Attribute trong C# định nghĩa trong namespace System.ComponentModel.DataAnnotations, có các loại gồm:

  • Các Attribute để kiểm tra dữ liệu (Validation Attribute)
  • Các Attribute hiện thị (Display Attribute), điều khiển dữ liệu trong lớp hiện thị thế nào trong UI
  • Modelling Attribute

Hãy thử xem một vài Attribute có sẵn:

Required Dữ liệu phải được thiết lập (!=null)
[Required (ErrorMessage="{0} cần thiết lập")]
StringLength Thiết lập độ dài trường dữ liệu
[StringLength (20,MinimumLength=3, ErrorMessage="Phải dài 3 đến 20 ký tự")]
DataType Chỉ ra dữ liệu phải liên kết phù hợp với một kiểu nào đó
[DataType(DataType.Text)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.EmailAddress)]
/.. Date, DateTime, Html, ImageUrl, MultilineText, Password, Time, Url
Range Chỉ ra dữ liệu phải trong khoảng nào đó
[Range(18,99, ErrorMessage="Tuổi từ 18 đến 99")]
[Range(typeof(DateTime), "1/2/2004", "3/4/2004",
        ErrorMessage = "Value for {0} must be between {1} and {2}")]
Phone [Phone] dữ liệu phải là dạng số điện thoại
EmailAddress [EmailAddress] dữ liệu phải là dạng email

Ví dụ:

Employer.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace CS026_Attribute {

  public class Employer {
    [Required (ErrorMessage = "Employee {0} is required")]
    [StringLength (100, MinimumLength = 3, ErrorMessage = "Tên từ 3 đến  100 ký tự")]
    [DataType (DataType.Text)]
    public string Name { get; set; }

    [Range (18, 99, ErrorMessage = "Age should be between 18 and 99")]
    public int Age { get; set; }


    [DataType (DataType.PhoneNumber)]
    [Phone]
    public string PhoneNumber { set; get; }

    [DataType (DataType.EmailAddress)]
    [EmailAddress]
    public string Email { get; set; }

  }

}

Để kiểm tra các dữ liệu phù hợp thiết lập bởi Attribute, thì dùng lớp ValidationContext (System.ComponentModel.DataAnnotations), ví dụ:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

// ...
public static void checkValidationContext()
{
    Employer user    = new Employer();
    user.Name        = "AF";
    user.Age         = 6;
    user.PhoneNumber = "1234as";
    user.Email       = "test@re";


    ValidationContext context       = new ValidationContext(user, null, null);
    // results - lưu danh sách ValidationResult, kết quả kiểm tra
    List<ValidationResult> results  = new List<ValidationResult>();
    // thực hiện kiểm tra dữ liệu
    bool valid = Validator.TryValidateObject(user, context, results, true);

    if (!valid)
    {
        // Duyệt qua các lỗi và in ra
        foreach (ValidationResult vr in results)
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.Write($"{vr.MemberNames.First(), 13}");
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"    {vr.ErrorMessage}");
        }
    }
}
// ...

Mã nguồn CS026_Attribute hoặc tải về cs026


Đăng ký nhận bài viết mới
Type (Bài trước)