具有RegularExpression属性的Asp.Net MVC 3.0模型本地化

时间:2012-05-26 12:25:46

标签: c# asp.net-mvc-3 localization data-annotations validationattribute

我在自定义DataAnnotationsModelMetadataProvider类中编写了自定义错误消息本地化逻辑。它与内置的StringLengthAttribute或RequiredAttribute验证错误消息一起正常工作。但是我的自定义派生的RegularExpressionAttribute类遇到了问题。我使用的逻辑如下所示:

public class AccountNameFormatAttribute : RegularExpressionAttribute {
    public AccountNameFormatAttribute()
      : base(Linnet.Core.Shared.RegExPatterns.AccountNamePattern) {

    }   

    public override string FormatErrorMessage(string name) {
      return string.Format("{0} field must contain only letters, numbers or | . | - | _ | characters.", name);
    }
}

public class SignUpViewModel {
    [AccountNameFormat()]
    [StringLength(16, MinimumLength = 3)]
    [Required]
    [DisplayName("Account Name")]
    public string AccountName { get; set; }

    [Required]
    [DisplayName("Password")]
    [StringLength(32, MinimumLength = 6)]
    [DataType(System.ComponentModel.DataAnnotations.DataType.Password)]
    public string Password { get; set; }

    // .... and other properties, quite similar ... //
}

public class MvcDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider {

    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) {
      MyMvcController myMvcController = context.Controller as MyMvcController; /* custom mvc controller, that contains methods for wcf service activations and common properties. */
      if (myMvcController == null) {
        return base.GetValidators(metadata, context, attributes);
      }
      List<Attribute> newAttributes = new List<Attribute>();
      foreach (Attribute att in attributes) {
        if (att.GetType() != typeof(ValidationAttribute) && !att.GetType().IsSubclassOf(typeof(ValidationAttribute))) {
          // if this is not a validation attribute, do nothing.
          newAttributes.Add(att);
          continue;
        }
        ValidationAttribute validationAtt = att as ValidationAttribute;
        if (!string.IsNullOrWhiteSpace(validationAtt.ErrorMessageResourceName) && validationAtt.ErrorMessageResourceType != null) {
          // if resource key and resource type is already set, do nothing.
          newAttributes.Add(validationAtt);
          continue;
        }
        string translationKey = "MvcModelMetaData.ValidationMessages." + metadata.ModelType.Name + (metadata.PropertyName != null ? "." + metadata.PropertyName : string.Empty) + "." + validationAtt.GetType().Name;
        string originalText = validationAtt.FormatErrorMessage("{0}"); /* non-translated default english text */

        // clonning current attiribute into a new attribute 
        // not to ruin original attribute for later usage 
        // using Activator.CreateInstance and then mapping with AutoMapper inside..
        var newAtt = this.CloneValidationAttiribute(validationAtt);  


        // fetching translation from database via WCF service...
        // At this point, i can see error strings are always translated.
        // And it works perfect with [Required], [StringLength] and [DataType] attributes.
        // But somehow it does not work with my AccountNameFormatAttribute on the web page, even if i give it the translated text as expected..
        // Even if its ErrorMessage is already set to translated text, 
        // it still displays the original english text from the overridden FormatErrorMessage() method on the web page. 
        // It is the same both with client side validation or server side validation.
        // Seems like it does not care the ErrorMessage that i manually set.
        newAtt.ErrorMessage = myMvcController.Translations.GetTranslation(translationKey, originalText);

        newAttributes.Add(newAtt);
      }
      IEnumerable<ModelValidator> result = base.GetValidators(metadata, context, newAttributes);
      return result;
    }

    private ValidationAttribute CloneValidationAttiribute(ValidationAttribute att) {
      if (att == null) {
        return null;
      }
      Type attType = att.GetType();
      ConstructorInfo[] constructorInfos = attType.GetConstructors();
      if (constructorInfos == null || constructorInfos.Length <= 0) {
        // can not close..
        return att;
      }
      if (constructorInfos.Any(ci => ci.GetParameters().Length <= 0)) {
        // clone with no constructor paramters.
        return CloneManager.CloneObject(att) as ValidationAttribute;
      }

      // Validation attributes that needs constructor paramters...
      if (attType == typeof(StringLengthAttribute)) {
        int maxLength = ((StringLengthAttribute)att).MaximumLength;
        return CloneManager.CloneObject(att, maxLength) as StringLengthAttribute;
      }

      return att;
    }
}

public class CloneManager {

    public static object CloneObject(object input) {
      return CloneObject(input, null);
    }

    public static object CloneObject(object input, params object[] constructorParameters) {
      if (input == null) {
        return null;
      }
      Type type = input.GetType();
      if (type.IsValueType) {
        return input;
      }
      ConstructorInfo[] constructorInfos = type.GetConstructors();
      if (constructorInfos == null || constructorInfos.Length <= 0) {
        throw new LinnetException("0b59079b-3dc4-4763-b26d-651bde93ba56", "Object type does not have any constructors.", false);
      }
      if ((constructorParameters == null || constructorParameters.Length <= 0) && !constructorInfos.Any(ci => ci.GetParameters().Length <= 0)) {
        throw new LinnetException("f03be2b9-b629-4a72-b025-c7a87924d9a4", "Object type does not have any constructor without parameters.", false);
      }
      object newObject = null;
      if (constructorParameters == null || constructorParameters.Length <= 0) {
        newObject = Activator.CreateInstance(type);
      } else {
        newObject = Activator.CreateInstance(type, constructorParameters);
      }
      return MapProperties(input, newObject);
    }

    private static object MapProperties(object source, object destination) {
      if (source == null) {
        return null;
      }
      Type type = source.GetType();
      if (type != destination.GetType()) {
        throw new LinnetException("e67bccfb-235f-42fc-b6b9-55f454c705a8", "Use 'MapProperties' method only for object with same types.", false);
      }
      if (type.IsValueType) {
        return source;
      }
      var typeMap = AutoMapper.Mapper.FindTypeMapFor(type, type);
      if (typeMap == null) {
        AutoMapper.Mapper.CreateMap(type, type);
      }
      AutoMapper.Mapper.Map(source, destination, type, type);
      return destination;
    }
}

2 个答案:

答案 0 :(得分:0)

似乎我的逻辑实际上是一种奇怪的方法。

我发现为每种类型的验证attiributes制作自定义DataAnnotationsModelValidators。然后在Validate()和GetClientValidationRules()方法中翻译ErrorMessages。

public class MvcRegularExpressionAttributeAdapter : RegularExpressionAttributeAdapter {
    public MvcRegularExpressionAttributeAdapter(ModelMetadata metadata, ControllerContext context, RegularExpressionAttribute attribute)
      : base(metadata, context, attribute) {
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
      return MvcValidationResultsTranslation.TranslateClientValidationRules(base.GetClientValidationRules(), this.Metadata, this.ControllerContext, this.Attribute);
    }

    public override IEnumerable<ModelValidationResult> Validate(object container) {
      return MvcValidationResultsTranslation.TranslateValidationResults(base.Validate(container), this.Metadata, this.ControllerContext, this.Attribute);
    }
}

 public class MvcValidationResultsTranslation {
    public static IEnumerable<ModelClientValidationRule> TranslateClientValidationRules(IEnumerable<ModelClientValidationRule> validationRules, ModelMetadata metadata, ControllerContext context, ValidationAttribute validationAttribute) {
      if (validationRules == null) {
        return validationRules;
      }
      MvcController mvcController = context.Controller as MvcController;
      if (mvcController == null) {
        return validationRules;
      }
      if (!string.IsNullOrWhiteSpace(validationAttribute.ErrorMessageResourceName) && validationAttribute.ErrorMessageResourceType != null) {
        // if resource key and resource type is set, do not override..         
        return validationRules;
      }
      string translatedText = GetTranslation(metadata, mvcController, validationAttribute);
      foreach (var validationRule in validationRules) {
        List<string> msgParams = new List<string>();
        msgParams.Add(!string.IsNullOrEmpty(metadata.DisplayName) ? metadata.DisplayName : metadata.PropertyName);
        if (validationRule.ValidationParameters != null) {
          msgParams.AddRange(validationRule.ValidationParameters.Where(p => p.Value.GetType().IsValueType || p.Value.GetType().IsEnum).Select(p => p.Value.ToString()));
        }
        validationRule.ErrorMessage = string.Format(translatedText, msgParams.ToArray());
      }
      return validationRules;
    }    

    public static IEnumerable<ModelValidationResult> TranslateValidationResults(IEnumerable<ModelValidationResult> validationResults, ModelMetadata metadata, ControllerContext context, ValidationAttribute validationAttribute) {
      if (validationResults == null) {
        return validationResults;
      }
      MvcController mvcController = context.Controller as MvcController;
      if (mvcController == null) {
        return validationResults;
      }
      if (!string.IsNullOrWhiteSpace(validationAttribute.ErrorMessageResourceName) && validationAttribute.ErrorMessageResourceType != null) {
        // if resource key and resource type is set, do not override..         
        return validationResults;
      }
      string translatedText = GetTranslation(metadata, mvcController, validationAttribute);
      List<ModelValidationResult> newValidationResults = new List<ModelValidationResult>();
      foreach (var validationResult in validationResults) {
        ModelValidationResult newValidationResult = new ModelValidationResult();
        newValidationResult.Message = string.Format(translatedText, (!string.IsNullOrEmpty(metadata.DisplayName) ? metadata.DisplayName : metadata.PropertyName));
        newValidationResults.Add(newValidationResult);
      }
      return newValidationResults;
    }
}

答案 1 :(得分:0)

您可以使用我的Griffin.MvcContrib来更轻松地进行本地化。

  1. 使用nuget下载griffin.mvccontrib
  2. 按照here所述定义字符串表。
  3. 直接在视图模型中使用常规[RegularExpression]属性。
  4. 将其添加到字符串表中:

    SignUpViewModel_AccountName_RegularExpression "{0} field must contain only letters, numbers or | . | - | _ | characters.
    

    就是这样..