Lars Woetmann Pedersen
Jul 5, 2013
  27733
(2 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

valdis
valdis Jul 9, 2013 11:24 PM

Great idea! :)
What I probably would be looking for is to elimate last 2 lines where you are manually "connecting" meta data provider cache dictionary content with current page property value for particular model validation message. This is pretty subject for automatization :) Anyway - thanks for an idea!

Sep 4, 2018 05:50 PM

In case anyone references this later, the code above has a little bug in it.  The add statement to the list of error messages for each view model property fails to account for the fact that there should only be one error message per validation attribute type.  As is, the list will grow longer and longer when the page is loaded.  Here's my fix:

public void SetValidationErrorMessage(Type containerType, string propertyName, Type validationAttribute, string errorMessage)
		{
			if (!string.IsNullOrWhiteSpace(errorMessage))
			{
				Tuple key = new Tuple(containerType, propertyName);
				if (!_errorMessageDictionary.ContainsKey(key))
				{
					_errorMessageDictionary[key] = new List>();
				}

				Tuple value = new Tuple(validationAttribute, errorMessage);

				//Check if there is already a runtime error message for this validation attribute
				if (_errorMessageDictionary[key].Any(e => e.Item1 == validationAttribute))
				{
					int index = _errorMessageDictionary[key].FindIndex(e => e.Item1 == validationAttribute);
					_errorMessageDictionary[key][index] = value;
				}
				else
				{
					_errorMessageDictionary[key].Add(value); 
				}
			}
		}

Please login to comment.
Latest blogs
Introducing AI Assistant v4 for Optimizely CMS 12 and 13

Epicweb AI Assistant v4.0 adds full support for Optimizely CMS 13 on .NET 10 while staying compatible with CMS 12 on .NET 8, plus new AI Chat tools...

Luc Gosso (MVP) | Apr 20, 2026 |

Remote Debugging in Optimizely DXP: What Is Actually Possible?

Introduction At SYZYGY Techsolutions , we support Optimizely DXP projects at scale, so continuously  identifying  the right tools and approaches fo...

Mike | Apr 20, 2026

Removing Unused Properties in Optimizely CMS 13

Learn how to remove orphaned property definitions in Optimizely CMS 13. Explore API updates for IContentTypeRepository and how to safely use...

Stuart | Apr 17, 2026 |

How to Remove the "Paste formatting options" Dialog in Optimizely CMS 12

If you've upgraded from an older Optimizely CMS solution lately, you may have noticed a dialog popping up every time an editor pastes content from...

Henning Sjørbotten | Apr 17, 2026 |

Creating an admin tool - unused assets

Let's make an admin tool to clean unused assets and see how to extend your favorite CMS with custom tools and menues! We will build a tool step by...

Daniel Ovaska | Apr 15, 2026

Running Optimizely CMS on .NET 11 Preview

Learn how to run Optimizely CMS on the .NET 11 preview with a single-line change. Explore performance gains, PGO improvements, and future-proofing...

Stuart | Apr 15, 2026 |