由同一屬性多次註釋的欄位
對於我的 ASP.NET MVC 應用程序,我創建了自定義驗證屬性,並指出可以為單個欄位或屬性指定多個它的實例:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] public sealed class SomeAttribute: ValidationAttribute我為這樣的屬性創建了驗證器:
public class SomeValidator : DataAnnotationsModelValidator<SomeAttribute>並將其連接
Application_Start到 Global.asaxDataAnnotationsModelValidatorProvider.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 個屬性 - 整個字母表都用完了,預計會有例外)
where
Storage是儲存目前 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) { ... }, ''); });要獲得完整的解決方案,請瀏覽此來源。