FluentValidation 實作多國語系紀錄

關鍵問題資訊

FluentValidation 本身已支援多國語系。在錯誤訊息會依當時語系跳出相應語系錯誤訊息,不過其中的 {PropertyName} 的語系應沒有多國的欄位名稱設定所以不會應語系變更。解法已知有二:

一、可以使用.WithName()指令指定錯誤訊息的{PropertyName}

二、改寫DisplayNameResolver,經由自訂的PropertyNameAttribute欄位屬性拿取當時語系的{PropertyName}

多國語系時的自訂錯誤訊息問題

自訂訊息就用 .WithMessage() 指令指定錯誤訊息。

實作相關技術-如何取得現在語系

CultureInfo.CurrentCulture Property    // 取得當時語系
CultureInfo.CurrentUICulture Property  // 取得當時UI語系

開發環境

平台: .NET6 IDE: Visual Studio 2022 框架: Blazor WASM App

程式碼紀錄 - DisplayNameResolver

自訂 PropertyNameAttribute

[AttributeUsage(
   AttributeTargets.Field |
   AttributeTargets.Property,
   AllowMultiple = true)]
public class PropertyNameAttribute : Attribute
{
  public string Name { get; set; }
  public string Culture { get; set; }

  public PropertyNameAttribute(string name)
  {
    Name = name;
    Culture = string.Empty;
  }

  public PropertyNameAttribute(string name, string culture)
  {
    Name = name;
    Culture = culture;
  }
}

經由 PropertyNameAttribute 指定多國語系欄位名稱,範例:

public class OrderModel
{
  [PropertyName("名稱", "zh-TW")]
  [PropertyName("Name", "en-US")]
  public string Name { get; set; } = "郝聰明";

  [PropertyName("信用卡號", "zh-TW")]
  [PropertyName("Credit card No", "en-US")]
  public string CCNumber { get; set; } = "4012 8888 8888 1881";

  [PropertyName("地址", "zh-TW")]
  [PropertyName("Address", "en-US")]
  public AddressModel Address { get; set; } = new AddressModel();
  
  [...略...]
}

實作 DisplayNameResolver 依解析多國語系欄位名稱

/// global type handling helper
public static class GT
{
  /// 於 FluentValidation 註冊 DisplayNameResolver 解析程序。
  public static string? ResolveDisplayName(MemberInfo member)
  {
    //## 支援多國語系,情境如下:
    //# 依 [PropertyName("欄位名稱","zh-TW")] 取 PropertyName,限定語系為 "zh-TW"
    //# 依 [PropertyName("欄位名稱","en-US")] 取 PropertyName,限定語系為 "en-US"
    //# 依 [PropertyName("欄位名稱")] 取 PropertyName 不指定語系
    string culture = CultureInfo.CurrentUICulture.Name; // 取現在語系
    var withPropertyName = member?.GetCustomAttributes(typeof(PropertyNameAttribute), false)
                                  .Select(property => (PropertyNameAttribute)property)
                                  .Where(c => c.Culture == culture || c.Culture == string.Empty)
                                  .OrderByDescending(c => c.Culture)
                                  .FirstOrDefault();
    if (withPropertyName != null) return withPropertyName.Name;
    return null;
  }
}

套入 FluentValidation 以解析出多國語系欄位名稱

//## 多國語系 - FluentValidation
//※ 系統一開始就執行此註冊動作,就是 Program.cs 第一行執行指令。
FluentValidation.ValidatorOptions.Global.DisplayNameResolver = 
    (type, member, expression) => GT.ResolveDisplayName(member);

程式碼紀錄 - WithName

直接在 Fluent 的 Validattor 依現在語系指定多國欄位名稱。

/// 客製錯誤訊
public class OrderValidator : AbstractValidator<OrderModel>
{
  public OrderValidator()
  {
    var culture = CultureInfo.CurrentUICulture; //取現在語系
    bool zhTW = culture.Name == "zh-TW";
    bool enUS = culture.Name == "en-US";

    // 比如未修改前是這樣
    RuleFor(x => x.Name)
        .NotEmpty()
        .Length(1, 20);
    
    // 依語系指定欄位名稱。
    RuleFor(x => x.Name)
        .NotEmpty()
        .Length(1, 3)
        .WithName(x => zhTW ? "名稱" : "Name"); 
    
    // 依語系指定完整錯誤訊息。    
    RuleFor(x => x.Name)
        .NotEmpty().WithMessage(zhTW ? "{PropertyName} 不可空白哦~" : "{PropertyName} no empty ~。")
        .Length(1, 3).WithMessage(zhTW ? "{PropertyName} 長度不合3~" : "{PropertyName} length invalid ~。")
        .WithName(x => zhTW ? "客製化名稱" : "Customized Name");    

    [...略...]
  }
}

完整程式碼

參考文章

Removed inferring property names from [Display] attribute

Last updated