我在自定义 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;
}
}