Вопрос

I am currently working with MVC4 data annotations to handle validation. I am working on a site that will be very much international and as such I keep all of my text in resource files.

I also want to keep regular expressions for validation in resource files so I can use the same code to check, for example, Post Codes (UK) and Zip Codes (US) just by using a different RegEx (and resources for the different names etc).

I have the below attribute which is already pulling the error message from a resource file. How can I have it get the regex from a resource file too?

[RegularExpression(@"^[\w]{1,2}[0-9]{1,2}[\w]?\s?[0-9]{1,2}[\w]{1,2}$", ErrorMessageResourceType = typeof(Resources.ValidationMessages), ErrorMessageResourceName = "validPostcode")]

EDIT (AGAIN)

Where I am now

Following the answer below and some additional searching around, I have the following:

In Global.asax.cs I have added the below line to ensure client side validation is invoked

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalisedAttribute), typeof(RegularExpressionAttributeAdapter));

In my model, I have this call to the attribute extension

[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]

And finally, the attribute extension for localised regex validation

public class LocalisedAttribute : RegularExpressionAttribute
{
    public LocalisedAttribute(Type resource, string regularExpression, string errorMessage) 
        : base(GetRegex(regularExpression))
    {
        ErrorMessageResourceType = resource;
        ErrorMessageResourceName = errorMessage;
    }

    private static string GetRegex(string value) 
    {
        return Resources.FormValidation.ResourceManager.GetString(value);


    }
}

This works, but ONLY the first time I use it when starting the application.

I am going to open another question to get around that problem - it's not directly related to the original request, doesn't seem to be relevant to most peoples implementation and doesn't seem to be specific to data annotations.

Это было полезно?

Решение

I already have some extended kind of RegularExpressionAttribute implementation, that allows to use resources for regex pattern. It looks like:

public class RegularExpressionExAttribute : RegularExpressionAttribute, IClientValidatable
{        
    private Regex regex { get; set; }
    private string pattern;

    private string resourceName;
    private Type resourceType;

    /// <summary>
    /// constructor, calls base with ".*" basic regex
    /// </summary>
    /// <param name="resName">resource key</param>
    /// <param name="resType">resource type</param>
    public RegularExpressionExAttribute(string resName, Type resType)
        : base(".*")
    {
        resourceName = resName;
        resourceType = resType;
    }

    /// <summary>
    /// override RegularExpressionAttribute property
    /// </summary>
    public new string Pattern
    {
        get
        {
            SetupRegex();
            return pattern;
        }
    }

    /// <summary>
    /// loads regex from resources
    /// </summary>
    private void SetupRegex()
    {
        ResourceAccessor ra = new ResourceAccessor(resourceName, resourceType);
        pattern = ra.resourceValue;
        regex = new Regex(pattern);
    }

    /// <summary>
    /// override validation with our regex
    /// </summary>
    /// <param name="value">string for validation</param>
    /// <returns></returns>
    public override bool IsValid(object value)
    {
        SetupRegex();
        string val = Convert.ToString(value);
        if (string.IsNullOrEmpty(val))
            return true;
        var m = regex.Match(val);
        return (m.Success && (m.Index == 0));
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metaData, ControllerContext controllerContext)
    {
        yield return new ModelClientValidationRegexRule(base.ErrorMessageString, this.Pattern);
    }
}

Also it's using ResourceAccessor class to get regex out of resources

public class ResourceAccessor
{
    private string resourceName;
    private Type resourceType;
    private Func<string> accessor;
    private string _resourceValue;

    public ResourceAccessor(string resourceName, Type resourceType)
    {
        this.resourceName = resourceName;
        this.resourceType = resourceType;
    }

    public string resourceValue
    {
        get
        {
            SetupAccessor();
            return accessor();
        }
    }

    private void SetupAccessor()
    {
        if (accessor != null) //already set
            return;
        string localValue = _resourceValue;
        bool flag1 = !string.IsNullOrEmpty(resourceName);
        bool flag2 = !string.IsNullOrEmpty(localValue);
        bool flag3 = resourceType != (Type)null;
        if (flag1 == flag2)
        {
            throw new InvalidOperationException("Can't set resource value");
        }
        if (flag3 != flag1)
        {
            throw new InvalidOperationException("Resource name and type required");
        }
        if (flag1)
            PropertyLookup();
        else
        {
            accessor = (Func<string>)(() => localValue);
        }
    }

    private void PropertyLookup()
    {
        if (resourceType == (Type)null || string.IsNullOrEmpty(resourceName))
        {
            throw new InvalidOperationException("Resource name and type required");
        }

        PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        if (property != (PropertyInfo)null)
        {
            MethodInfo getMethod = property.GetGetMethod(true);
            if (getMethod == (MethodInfo)null || !getMethod.IsAssembly && !getMethod.IsPublic)
                property = (PropertyInfo)null;
        }
        if (property == (PropertyInfo)null)
        {
            throw new InvalidOperationException("Resource type doesn't have property");
        }
        else if (property.PropertyType != typeof(string))
        {
            throw new InvalidOperationException("Resource type must be string");
        }
        else
        {
            accessor = (Func<string>)(() => (string)property.GetValue((object)null, (object[])null));
        }
    }
}

And here is usage samples:

public class SignUpInput
{        
    [RegularExpressionEx("EmailValidationRegex", typeof(LocalizedResources), ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = "invalidEmail")]     
    public string Email { get; set; }
}

Другие советы

I think yuo can extend RegularExpressionAttribute

public class PostCodeValidationAttribute : RegularExpressionAttribute
    {
        public PostCodeValidationAttribute()
            : base(Resources.PostCodeValidationExpression)
        {
        }
    }

UPDATE

Put culture info name in session for example accordingly with user choice. And use it in

ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture(userCulture));

At first you can test it with hardcode value. Something like this

ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture("en-GB"));

instead

ResourceManager.GetString(value, CultureInfo.CreateSpecificCulture(currentCulture));

or in base constructor

base(GetRegex(regularExpression, ""en-GB""))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top