Dot-Net

由同一屬性多次註釋的欄位

  • August 22, 2014

對於我的 ASP.NET MVC 應用程序,我創建了自定義驗證屬性,並指出可以為單個欄位或屬性指定多個它的實例:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class SomeAttribute: ValidationAttribute

我為這樣的屬性創建了驗證器:

public class SomeValidator : DataAnnotationsModelValidator<SomeAttribute>

並將其連接Application_Start到 Global.asax

DataAnnotationsModelValidatorProvider.RegisterAdapter(
   typeof (SomeAttribute), typeof (SomeValidator));

最後,如果我以所需的方式使用我的屬性:

[SomeAttribute(...)]  //first
[SomeAttribute(...)]  //second
public string SomeField { get; set; }

當框架執行驗證時,只呼叫第一個屬性實例。第二個好像死了。我注意到在每個請求期間只創建一個驗證器實例(對於第一個註釋)。

如何解決這個問題並觸發所有屬性?

讓我自己回答。來自 MSDN http://msdn.microsoft.com/en-us/library/system.attribute.typeid.aspx,http://msdn.microsoft.com/en-us/library/6w3a7b50.aspx):

當您定義自定義屬性並將 AttributeUsageAttribute.AllowMultiple 設置為 true 時,您必須覆蓋 Attribute.TypeId 屬性以使其唯一。如果您的屬性的所有實例都是唯一的,請覆蓋 Attribute.TypeId 以返回您的屬性的對象標識。如果您的屬性只有某些實例是唯一的,請從 Attribute.TypeId 返回一個值,該值將在這些情況下返回相等性。例如,某些屬性具有充當唯一鍵的建構子參數。對於這些屬性,從 Attribute.TypeId 屬性返回建構子參數的值。

正如所實現的那樣,這個標識符僅僅是屬性的類型。但是,唯一標識符旨在用於標識相同類型的兩個屬性。

總結一下:

TypeId 被記錄為“用於標識相同類型的兩個屬性的唯一標識符”。預設情況下,TypeId 只是屬性的類型,因此當遇到兩個相同類型的屬性時,MVC 驗證框架將它們視為“相同”。

實施:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class SomeAttribute: ValidationAttribute
{
   private string Parameter { get; set; }
   public override object TypeId
   {
       get { return string.Format("{0}[{1}]", GetType().FullName, Parameter); } 
   }

   public SomeAttribute(string parameter)
   {
       Parameter = parameter;
   }                

這種創建 TypeId 的方式是在以下備選方案中選擇的:

  • 返回新對象-太多了,實例總是不同的,
  • 根據字元串參數返回雜湊碼 - 可能導致衝突(無限多的字元串不能單射映射到任何有限集 - 字元串的最佳唯一標識符是字元串本身,請參見此處)。

完成後,伺服器端驗證案例工作。當這個想法需要與不顯眼的客戶端驗證相結合時,問題就開始了。假設您以以下方式定義了自定義驗證器:

public class SomeAttributeValidator : DataAnnotationsModelValidator<SomeAttribute>
{
   public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
   {
       var rule = new ModelClientValidationRule {ValidationType = "someattribute"};
       rule.ValidationParameters.Add(...)
       yield return rule;
   }

有這個:

public class Model
{
   [SomeAttribute("first")]
   [SomeAttribute("second")]
   public string SomeField { get; set; }

導致以下錯誤:

不顯眼的客戶端驗證規則中的驗證類型名稱必須是唯一的。不止一次看到以下驗證類型:someattribute

如前所述,解決方案是擁有獨特的驗證類型。我們必須區分每個註冊的屬性實例,它已經被用來註解一個欄位,並為其提供相應的驗證類型:

public class SomeAttributeValidator : DataAnnotationsModelValidator<SomeAttribute>
{
   private string AnnotatedField { get; set; }
   public SomeAttributeValidator(
       ModelMetadata metadata, ControllerContext context, SomeAttribute attribute)
       : base(metadata, context, attribute)
   {
       AnnotatedField = string.Format("{0}.{1}", 
           metadata.ContainerType.FullName, metadata.PropertyName);
   }

   public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
   {
       var count = Storage.Get<int>(AnnotatedField) + 1;
       Storage.Set(AnnotatedField, count);

       var suffix = char.ConvertFromUtf32(96 + count); // gets a lowercase letter 
       var rule = new ModelClientValidationRule
       {
           ValidationType = string.Format("someattribute{0}", suffix)
       };

(驗證類型必須只包含小寫字母 - 使用上面的解決方案,如果您有超過 26 個屬性 - 整個字母表都用完了,預計會有例外)

whereStorage是儲存目前 http 請求數據的簡單實用程序:

internal class Storage
{
   private static IDictionary Items
   {
       get
       {
           if (HttpContext.Current == null)
               throw new ApplicationException("HttpContext not available.");
           return HttpContext.Current.Items;
       }
   }

   public static T Get<T>(string key)
   {
       if (Items[key] == null)
           return default(T);
       return (T)Items[key];
   }

   public static void Set<T>(string key, T value)
   {
       Items[key] = value;
   }
}

最後,JavaScript 部分:

$.each('abcdefghijklmnopqrstuvwxyz'.split(''), function(idx, val) {
   var adapter = 'someattribute' + val;
   $.validator.unobtrusive.adapters.add(adapter, [...], function(options) {
       options.rules[adapter] = {
           ...
       };
       if (options.message) {
           options.messages[adapter] = options.message;
       }
   });
});

$.each('abcdefghijklmnopqrstuvwxyz'.split(''), function(idx, val) {
   var method = 'someattribute' + val;
   $.validator.addMethod(method, function(value, element, params) {
       ...
   }, '');
});

要獲得完整的解決方案,請瀏覽此來源

引用自:https://stackoverflow.com/questions/25414296