Friday, November 6, 2015

ASP.NET MVC - Customizing RequiredAttribute to display required field validation errors in a different format than the default one

Consider a scenario where you want all Required validators of your MVC application to display error messages in a different format than the default one.
As of ASP.NET MVC 5, the default error message format for required field validator is "The <<field display name>> field is required." For example, "The User name field is required." or "The Password field is required."

But instead, you want them to display in a different format, like - "Please enter value in <<field display name>>." Examples - "Please enter value in User name input box", or "Please enter value in Password input box".

One option is to set "ErrorMessage" property of Required data annotation for each and every fields where this validator is used. However, this can solve the purpose, but it will be tedious and importantly, maintenance nightmare to do so.

Perform following steps to customize default error message of Required validators, and apply them across the application.

Step - 1 Create following custom attribute (for instance, "CustomizedRequiredValidatorAdapter") inheriting from RequiredAttributeAdapter:

    public class CustomizedRequiredValidatorAdapter: RequiredAttributeAdapter
    {
        public CustomizedRequiredValidatorAdapter(ModelMetadata metadata,
                                    ControllerContext context,
                                    RequiredAttribute attribute)
            : base(metadata, context, attribute)
        {
            attribute.ErrorMessage = "Please enter value in {0}.";
        }
    }

This requires "System.Web.Mvc", and "System.ComponentModel.DataAnnotations" namespaces

Step - 2 Replace default RequiredAttribute validator by the one created in Step - 1

Add following in the Application_Start event of Global.asax.cs file:

DataAnnotationsModelValidatorProvider.RegisterAdapter(
                typeof(RequiredAttribute),
                typeof(CustomizedRequiredValidatorAdapter));

That's it! This is all you need to do. Now try running your MVC application and see the error messages on required validation failures and you should see the customized messages.

Further, if you have Globalization implemented, you can set attribute.ErrorMessageResourceType, and attribute.ErrorMessageResourceName in the custom attribute (step - 1) to get the string Error message string from resources file.

ASP.NET MVC >> Using enumerations in AuthorizeAttribute to allow the actions to be invoked by multiple roles

Consider a scenario where you need all/ some of the actions of a controller to be invoked by users having specific roles. For example, a "AdminController" which can be accessed only be users having "Admin" or "SuperUser" roles.

Now, its easy to pass multiple roles as a string in Authorize attribute as below:
[Authorize(Roles="Roles,SuperUser")]

However, this is an absolute maintenance nightmare, because if any of the roles are renamed you will need to remember to update at multiple places.

Imagine creating enum as below

    public enum Roles
    {
        User,
        Admin,
        SuperUser
    }

But as the Roles parameter in Authorize attribute is of string type, it wont allow you to pass multiple enum values there.

In order to fix this issue, we need to to create a new attribute inheriting from MVC's AuthorizeAttribute:

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    public class AuthorizeEnum: AuthorizeAttribute
    {
        public AuthorizeEnum(params object[] roles)
        {
            if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
                throw new ArgumentException("roles");

            this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
        }
    }

Now, this attribute can be used to decorate the controllers and/ or actions to pass multiple roles as enum values instead of strings.

[AuthorizeEnum(Roles.User, Roles.Admin)]
public partial class AdminController 
{
..........
}