Quick tip: Sitecore per-site language embedding

Sitecore does not come with the an out-of-the-box way of being able to have language embedding turned on or off at the site level but it can easily be done using different LinkProviders.
1. Create two LinkProviders, one that has languageEmbedding turned on and one with it turned off. (Both referenced under <linkManagers><providers>).

2. Create a custom attribute for the <site> node called linkProvider and allow the person configuring to choose which provider to use at the site level.

3. When a link is rendered, the linkProvider would then be determined within a switcher LinkProvider which looks at the SiteContext attributes of the current site to choose the proper LinkProvider.

Advertisements

Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 3 (Custom validation, soup to nuts)

So you wanna provide a custom validation data annotation that would function in both the server and client validation arena?

international

Below we have a scenario where there are two form fields highlighted, Password and VerifyPassword. Imagine that we would like to make the VerifyPassword property required IF the Password field was filled out. [This could exist, for example, on a ‘change password’ form.]

The DataAnnotation provided within ASP.NET MVC don’t have a built-in attribute to support this so we will build one based on the Required attribute.

1) Create a custom attribute class that inherits from RequiredAttribute and IClientValidatable. If either of these concepts are new, please see Part 1 and Part 2 of the series.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public class RequiredIfPopulated : RequiredAttribute, IClientValidatable
    {
        public  string PropertyName { get; set; }

        public RequiredIfPopulated(string propertyName)
        {
            PropertyName = propertyName;
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            Object instance = context.ObjectInstance;
            Type type = instance.GetType();
            Object propertyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
            if (propertyvalue != null)
            {
                ValidationResult result = base.IsValid(value, context);
                return result;
            }
            return ValidationResult.Success;
        }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var clientValidationRule = new ModelClientValidationRule()
            {
                ErrorMessage = FormatErrorMessage(ErrorMessage),
                ValidationType = "requiredif"
            };

            clientValidationRule.ValidationParameters.Add("other", OtherProperty));

            return new[] { clientValidationRule };
        }
    }

Something new added to the class is the IsValid override. This gets the value of the parameter, PropertyName (in this case, Password), and validates that, if it is populated, that the source property (i.e. VerifyPassword) is also populated. If it is not, the validation fails on the server side.

The GetClientValidationRules method is similar to Part 2‘s CompareTranslated custom attribute. The primary difference is that the “ValidationType” is defined as ‘requiredif’ — a validation rule not available in jQuery.validate.js. You can name this new ValidationType anything javascript-acceptable (i.e. no script keywords), but it must be 100% lowercase.

2) Decorate the source property (VerifyPassword) with the new custom attribute.

[MinLengthTranslated(6, ErrorMessage = "PasswordMinLength")]
public string Password {get; set;}

[RequiredIfPopulated("Password", ErrorMessage = "VerifyPasswordRequired")]
[CompareTranslated("Password", ErrorMessage = "VerifyPasswordInvalid")]
public string VerifyPassword {get; set;}

3) Now, for the javascript.

$.validator.addMethod(
    "requiredif",
    function (val, element, other) {
        var target = $(other);
        return target.val().length == 0 || $.trim(val).length > 0 || (target.val().length > 0 && $.trim(val).length > 0);
    });

$.validator.unobtrusive.adapters.add('requiredif', ['other'],
    function (options) {
        options.rules["requiredif"] = options.params;
        options.messages["requiredif"] = options.message;
    });

$(".myform").data('validator', null);
$.validator.unobtrusive.parse($(".myform"));

First, we have to add a new function to jQuery.validator called ‘requiredif’ (exactly the same name as the ValidationType declared in GetClientValidationRules()). This function is added to the validation list with $.validator.addMethod and contains validation logic in javascript. The first param of the function is the value of the field being evaluated (VerifyPassword). The second is the VerifyPassword element, and the third is the ‘other’ parameter, in this case, Password.

Second, we have to add an adapter to $.validator.unobstrusive.adapters, using the ‘add; function, passing in: a) the name of the validator; b) is the ‘other’ parameter, in this case, Password. … remember the param “other” we declared in GetClientValidationRules()? Here’s one place we are using it. c) Finally, the method ties the rule and message for the attribute into the unobtrusive validator.

And last but not least, we have two very important lines of code. Your page and custom client validation may run just fine without it. It all depends on how you load the previous two snippets of javascript. Here’s the trick … they need to be loaded after the 3 jquery includes (defined in Part 2) but prior to DOM ready and certainly NOT in (document).ready.

These two lines of code first, remove the previous set of validators, and then reload them. In effect, they have a chance to grab all the ones that were missed the first time around.

Stay tuned for future posts on fluent validation and perhaps some validator troubleshooting.

Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 2 (Client validation)

So you wanna extend your custom MVC data annotations for form validations for client-side validation?

internationalI suggest that you read Part 1 for context and to ensure that you have the prerequisites to fully understand the series.

In the first post of this series, we built custom attributes to allow the language translation of the error messaging. These custom attributes need to be extended to support client validation.

1) Below is the data model properties decorated with custom attributes that provide translated error messaging (via Sitecore.Globalization.Translate and the dictionary domain) for server validation:

public class RequiredTranslated : RequiredAttribute
    {
        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class MinLengthTranslated : MinLengthAttribute
    {
        public MinLengthTranslated(int length) : base(length) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class CompareTranslated : CompareAttribute
    {
        public CompareTranslated(string otherProperty) : base(otherProperty) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

These classes inherit from their ‘non-translated’ equivalents, “Required”, “MinLength”, and “Compare.”

To improve on this translated server validation, let’s add translated client validation.

Each custom attribute class will inherit from IClientValidatable. IClientValidatable requires implementation of a method called GetClientValidationRules() which ensures the propagation of the validation rule(s) within the validation markup and script.

RequiredAttribute -> RequiredTranslatedAttribute
Adding the method to RequiredTranslated is straightforward; implement the method for IClientValidatable and pass the translated text for the error messaging to the front-end.

MinLength -> MinLengthTranslated
For MinLengthTranslated, the front-end not only needs the error message but also the minimum length to properly validate with javascript (More on the javascript in the next section).

Compare -> CompareTranslated
CompareTranslated elevates the complexity of implementing IClientValidatable because we need to pass an existing property thru to the validator (in this case, the VerifyPassword property value needs to be evaluated against the Password property value). The script needs to know in what parameter the property will be passed  (below, that is the ‘other’ param). We also need to declare which validation rule (i.e. ValidationType) of jquery.validate that this property matches to.  In this case, it matches the jquery.validate rule of “equalto”.

2) Implement IClientValidatable for each custom attribute.

public class RequiredTranslated : RequiredAttribute, IClientValidatable
    {
        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationRequiredRule(Translate.Text(ErrorMessage)) };
        }
    }

public class MinLengthTranslated : MinLengthAttribute, IClientValidatable
    {
        public MinLengthTranslated(int length) : base(length) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationMinLengthRule(Translate.Text(ErrorMessage), Length) };
        }
    }

public class CompareTranslated : CompareAttribute, IClientValidatable
    {
        public CompareTranslated(string otherProperty) : base(otherProperty) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var clientValidationRule = new ModelClientValidationRule()
            {
                ErrorMessage = FormatErrorMessage(ErrorMessage),
                ValidationType = "equalto"
            };

            clientValidationRule.ValidationParameters.Add("other", OtherProperty));

            return new[] { clientValidationRule };
        }
    }

3) What happens when this new “GetClientValidationRules()” method is called when building the HTML for the page and what javascript is required?

In ASP.NET MVC 3 a new feature called “Unobtrustive Client Validation” was added which uses jQuery and jQuery.validate to perform validation based on the data annotations (and in our case, custom attributes). There are plenty of blogs that describe.

What do you need to use this?

First, turn on the functionality in the appSettings.config file:

configvalidatesettings

You will also need to include three javascript files (or their min equivalent) on your page:

1) jQuery
2) jQuery Validate
3) jQuery Validate Unobtrusive (plugin for ASP.NET MVC by Microsoft)

It all works as smooth as butter.  Love it.

What if you want to do some custom validation??  Glad you asked!  Check out Part 3 of this series, Custom Validation, Soup to Nuts.

Multi-lingual form validation using MVC DataAnnotation attributes and Sitecore – Part 1 (Custom attributes)

So you wanna use MVC data annotations for form validation in a multi-lingual Sitecore site?

internationalWith Sitecore wrapping a firm grip around MVC, it is important to have a solid validation strategy for your data models.  From home-grown controller action validation to fluent validation (new post coming soon …), there are a variety of ways to tackle it.  Making the site multi-lingual and with client validation adds some layers of complexity.

Much of the functionality has been around since MVC 3.  So, why create a new post about it now?  … as I was working on a time-sensitive project, I wasn’t able to find all of the answers to my translation questions in one (or two or three) spots.  I’m striving to knit all that’s out there together when one questions is asked.

Prerequisites:

In this post, I’ll be providing tips and tricks for using MVC DataAnnotation attributes to decorate your data model properties for validation while writing the error messages in the user’s chosen language using Sitecore.Globalization.Translate.Text().

1) Create your data model with desired DataAnnotation attributes

[DisplayName("Email Address")]
[Required(ErrorMessage = "Email address is required.")]
public string EmailAddress {get; set;}

[DisplayName("Password")]
[MinLength(6, ErrorMessage = "Password must be a minimum of 6 characters.")]
public string Password {get; set;}

[DisplayName("Verify Password")]
[Compare("Password", ErrorMessage = "Verify Password must match Password.")]
public string VerifyPassword {get; set;}

2) Create your form that supports your data model

@using (Html.BeginForm()) {

   @Html.ValidationSummary()
   @Html.LabelFor(x=>x.EmailAddress) 
   @Html.TextBoxFor(x => x.EmailAddress) 
   @Html.ValidationMessageFor(x => x.EmailAddress) 

   @Html.LabelFor(x=>x.Password) 
   @Html.PasswordFor(x => x.Password) 
   @Html.ValidationMessageFor(x => x.Password) 

   @Html.LabelFor(x=>x.VerifyPassword) 
   @Html.PasswordFor(x => x.VerifyPassword) 
   @Html.ValidationMessageFor(x => x.VerifyPassword) 

   @Html.ActionLink("Save", "SaveAction", "MainController")
}

screen1

Nice! Server validation works well, however, we want multi-lingual and the attribute argument (in this case, the ErrorMessage) “must be a constant expression, typeof expression, or array creation expression of an attribute parameter type”. It must be a compile-time constant … Sitecore.Globalization.Translate.Text() or any method cannot be used as a replacement for the string within the attribute argument.

3) Create custom attributes to support translating text for multi-lingual sites. Creating custom attributes provide the ability to override and customize methods of the attribute from which you are inheriting.

public class RequiredTranslated : RequiredAttribute
    {
        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class MinLengthTranslated : MinLengthAttribute
    {
        public MinLengthTranslated(int length) : base(length) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

public class CompareTranslated : CompareAttribute
    {
        public CompareTranslated(string otherProperty) : base(otherProperty) { }

        public override string FormatErrorMessage(string name)
        {
            return Translate.Text(base.FormatErrorMessage(name));
        }
    }

These classes inherit from their ‘non-translated’ equivalents, “Required”, “MinLength”, and “Compare.”

4) Decorate your data model properties with your custom attributes. Please make sure that you swap out the message for a translated key that matches your message from your domain dictionary.

For example, “EmailAddressRequired” is my domain dictionary key for the string, “Email Address is required” (and its various other language versions).

[RequiredTranslated(ErrorMessage = "EmailAddressRequired")]
public string EmailAddress {get; set;}

[MinLengthTranslated(6, ErrorMessage = "PasswordMinLength")]
public string Password {get; set;}

[CompareTranslated("Password", ErrorMessage = "VerifyPasswordInvalid")]
public string VerifyPassword {get; set;}

With this small step of adding custom attributes, you have an easy way to handle multi-lingual server validation.

If you’d like to see how you can use these custom attributes with client validation, see part 2.