Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data Annotations - using an attribute extension and storing regular expressions in a resource file

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.

like image 405
Alan Shortis Avatar asked Feb 12 '14 18:02

Alan Shortis


2 Answers

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; }
}
like image 196
Sergio Avatar answered Sep 30 '22 13:09

Sergio


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""))
like image 38
Daniil Grankin Avatar answered Sep 30 '22 13:09

Daniil Grankin