Take the community feedback survey now.

Lars Woetmann Pedersen
Jul 5, 2013
  21727
(0 votes)

MVC ValidationAttribute ErrorMessage populated at runtime

I am working on a project using EPiServer 7 and MVC 4. One of the nice features in MVC is ValidationAttributes in the model such as RequiredAttribute and RegularExpressionAttribute. These attributes give you server side validation on your model, if combined with jquery.validate and jquery.validate.unobtrusive you also get client side validation without writing any javascript yourself. This is very nice and keeps the code clean and it looks something like this:

In my controller I create a viewmodel and pass it to the view, using a viewmodel is optional but I like it and follow the pattern described by Joel Abrehamsson here: http://joelabrahamsson.com/episerver-and-mvc-what-is-the-view-model/

public class EducationPlanSearchController : ProtectedPageControllerBase<EducationPlanSearchPage>
{
    [HttpGet]
    public ActionResult Index(EducationPlanSearchPage currentPage)
    {
        EducationPlanSearchPageViewModel viewModel = new EducationPlanSearchPageViewModel(currentPage);
        return View(viewModel);
    }
 
    ... HttpPost part left out 
}

My viewmodel has one property its required and has to have a correct format:

public class EducationPlanSearchPageViewModel : PageViewModel<EducationPlanSearchPage>
{
    public EducationPlanSearchPageViewModel(EducationPlanSearchPage currentPage) : base(currentPage)
    {
    }
 
    [Required(ErrorMessage = "Compiletime error required")]
    [RegularExpression(@"^\d{6,6}-?\d{4,4}$", ErrorMessage = "Compiletime error format")]
    public string Cpr { get; set; }
}

The view is very simple it is just a form with one input field and a submit button:

@model Pdk.Website.Models.ViewModels.EducationPlanSearchPageViewModel
 
<div class="content-block">
    <div class="formLine">
        @using (Html.BeginForm())
        {
            @Html.ValidationSummary()
            @Html.EditorFor(m => m.Cpr)
            <input type="submit" value="@Model.CurrentPage.SearchButtonText"/>
        }
    </div>
</div>

In my layout I include the needed jquery files:

<script src="/Scripts/jquery-1.10.1.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

If someone press the submit button with an empty input field or one with the wrong format they will get an error message, and this happens client side, using the ErrorMessage defined in the attribute for the property in the viewmodel:

This is all standard MVC so far, there is just one problem with this because I would like to set the ErrorMessage on the ValidationAttribute runtime using an EPiServer page property. That would enable the editors to change the error message when they want to.

To do this I have to implement my own  model metadata provider which is used by  ASP.NET MVC to load the validation attributes, tell my IoC container to use this, and set the ErrorMessage when the viewmodel is created. First I create my own  model metadata provider:

public class MyModelMetaDataProvider : CachedDataAnnotationsModelMetadataProvider
{
    private readonly Dictionary<Tuple<Type, string>, List<Tuple<Type, string>>> _errorMessageDictionary = new Dictionary<Tuple<Type, string>, List<Tuple<Type, string>>>();
 
    public void SetValidationErrorMessage(Type containerType, string propertyName, Type validationAttribute, string errorMessage)
    {
        if (!string.IsNullOrWhiteSpace(errorMessage))
        {
            Tuple<Type, string> key = new Tuple<Type, string>(containerType, propertyName);
            if (!_errorMessageDictionary.ContainsKey(key))
            {
                _errorMessageDictionary[key] = new List<Tuple<Type, string>>();
            }
 
            Tuple<Type, string> value = new Tuple<Type, string>(validationAttribute, errorMessage);
            _errorMessageDictionary[key].Add(value);                
        }
    }
 
    protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
    {
        CachedDataAnnotationsModelMetadata model = base.CreateMetadataPrototype(attributes, containerType, modelType, propertyName);
 
        Tuple<Type, string> key = new Tuple<Type, string>(containerType, propertyName);
 
        if (_errorMessageDictionary.ContainsKey(key))
        {
            List<Tuple<Type, string>> errorMessageList = _errorMessageDictionary[key];
            foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>())
            {
                var value = errorMessageList.FirstOrDefault(t => t.Item1 == attribute.GetType());
                if (value != null)
                {
                    attribute.ErrorMessage = value.Item2;
                }                    
            }
        }
        return model;
    }
}

It contains a dictionary where I add the error messages I want to update, the key in the dictonary is the viewmodel type and property name, the value is the attribute type and error message. When a view with a model containing validation attributes is created  CreateMetadataPrototype is called and the ErrorMessages is set using this dictionary.

The viewmodel is updated to call SetValidationErrorMessage from the constructor, I get the new error messages from the PageData model, thus allowing the editors to edit it as a property on the page:

public class EducationPlanSearchPageViewModel : PageViewModel<EducationPlanSearchPage>
{
    public EducationPlanSearchPageViewModel(EducationPlanSearchPage currentPage) : base(currentPage)
    {
        MyModelMetaDataProvider metadataProvider = (MyModelMetaDataProvider)ModelMetadataProviders.Current;
        metadataProvider.SetValidationErrorMessage(GetType(), "Cpr", typeof(RequiredAttribute), currentPage.ErrorCprEmpty);
        metadataProvider.SetValidationErrorMessage(GetType(), "Cpr", typeof(RegularExpressionAttribute), currentPage.ErrorCprFormatWrong);
    }
 
    [Required]
    [RegularExpression(@"^\d{6,6}-?\d{4,4}$")]
    public string Cpr { get; set; }
}

Before it works I have to tell my IoC container to use my own ModelMetadataProvider:

container.For<ModelMetadataProvider>().Use<MyModelMetaDataProvider>();

Now when the Index action on EducationPlanSearchController is called the viewmodel is created and in its constructor the correct ErrorMessage is set on the current ModelMetadataProvider which is of the type MyModelMetaDataProvider and when the view is called the method CreateMetadataPrototype is called, and the correct ErrorMessage is set.

I hope this can help others in the same situation, I realize the code could use some more comments so don’t hesitate to ask if there is any problems with it. And if anybody has a cleaner solution to this problem, (apart from convincing the editor to give up runtime editing of error messages) then I would like the see it.

Jul 05, 2013

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - Opticon London 2025

This installment of a day in the life of an Optimizely OMVP gives an in-depth coverage of my trip down to London to attend Opticon London 2025 held...

Graham Carr | Oct 2, 2025

Optimizely Web Experimentation Using Real-Time Segments: A Step-by-Step Guide

  Introduction Personalization has become de facto standard for any digital channel to improve the user's engagement KPI’s.  Personalization uses...

Ratish | Oct 1, 2025 |

Trigger DXP Warmup Locally to Catch Bugs & Performance Issues Early

Here’s our documentation on warmup in DXP : 🔗 https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites What I didn...

dada | Sep 29, 2025

Creating Opal Tools for Stott Robots Handler

This summer, the Netcel Development team and I took part in Optimizely’s Opal Hackathon. The challenge from Optimizely was to extend Opal’s abiliti...

Mark Stott | Sep 28, 2025

Integrating Commerce Search v3 (Vertex AI) with Optimizely Configured Commerce

Introduction This blog provides a technical guide for integrating Commerce Search v3, which leverages Google Cloud's Vertex AI Search, into an...

Vaibhav | Sep 27, 2025

A day in the life of an Optimizely MVP - Opti Graph Extensions add-on v1.0.0 released

I am pleased to announce that the official v1.0.0 of the Opti Graph Extensions add-on has now been released and is generally available. Refer to my...

Graham Carr | Sep 25, 2025