World is now on Opti ID! Learn more

Lars Woetmann Pedersen
Jul 5, 2013
  21697
(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
Make Global Assets Site- and Language-Aware at Indexing Time

I had a support case the other day with a question around search on global assets on a multisite. This is the result of that investigation. This co...

dada | Jun 26, 2025

The remote server returned an error: (400) Bad Request – when configuring Azure Storage for an older Optimizely CMS site

How to fix a strange issue that occurred when I moved editor-uploaded files for some old Optimizely CMS 11 solutions to Azure Storage.

Tomas Hensrud Gulla | Jun 26, 2025 |

Enable Opal AI for your Optimizely products

Learn how to enable Opal AI, and meet your infinite workforce.

Tomas Hensrud Gulla | Jun 25, 2025 |

Deploying to Optimizely Frontend Hosting: A Practical Guide

Optimizely Frontend Hosting is a cloud-based solution for deploying headless frontend applications - currently supporting only Next.js projects. It...

Szymon Uryga | Jun 25, 2025

World on Opti ID

We're excited to announce that world.optimizely.com is now integrated with Opti ID! What does this mean for you? New Users:  You can now log in wit...

Patrick Lam | Jun 22, 2025

Avoid Scandinavian Letters in File Names in Optimizely CMS

Discover how Scandinavian letters in file names can break media in Optimizely CMS—and learn a simple code fix to automatically sanitize uploads for...

Henning Sjørbotten | Jun 19, 2025 |