top button
Flag Notify
    Connect to us
      Site Registration

Site Registration

Show Validation Messages Containing Images In ASP.NET MVC

+5 votes
342 views

ASP.NET MVC offers HTML helpers for displaying field level validation error messages as well as validation summary. However, these validation messages are displayed as a plain string. There is no inbuilt way to include images or HTML markup with the output of the validation helpers. In this article I will show how to overcome this limitation using two techniques so that you can display images along with the validation messages. The techniques I discuss include:

  • Use HTML markup in the validation message to display an image.
  • Using a CSS class to display an image.

Before I delve into more details, let's quickly see the model that this example is going to use:

public class Employee
{
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

The Employee model class contains three properties namely EmployeeID, FirstName and LastName. To keep the model clean you won't add data annotations here. You will add them to metadata class(s) as discussed later.

The Home controller that makes use of this model contains two actions as shown below:

public ActionResult Index()
{
    return View();
}


[HttpPost]
public ActionResult ProcessForm(Employee emp)
{
    if(ModelState.IsValid)
    {
        //do insert here
    }
    return View("Index", emp);
}

The Index() action simply displays the Index view like this:

image

The ProcessForm() action receives an instance of Employee model through model binding. If there are any errors they are shown like this:

image

Notice how images are being displayed by ValidationMessageFor() and ValidationSummary() helpers.

Using HTML markup in the validation message to display an image

Now let's create a metadata class that contains data annotations for the Employee class. This class - EmployeeMetadata - is shown below:

public class EmployeeMetadata
{
    [Required]
    [Range(1, int.MaxValue, 
     ErrorMessage = "<img src='/images/error.png' /> 
      Invalid EmployeeID!")]
    public int EmployeeID { get; set; }

    [Required]
    [StringLength(20, 
     ErrorMessage = "<img src='/images/error.png' /> 
      Invalid first name!")]
    public string FirstName { get; set; }

    [Required]
    [StringLength(20, 
     ErrorMessage = "<img src='/images/error.png' /> 
      Invalid last name!")]
    public string LastName { get; set; }
}

Notice how ErrorMessage property of [StringLength] attribute includes an image HTML markup tag. Once created attach this metadata class to the Employee class using [MetadataType] attribute.

[MetadataType(typeof(EmployeeMetadata))]
public class Employee
{
  ...
}

Add the Index view and change its content as per the following markup.

...
...
@using(Html.BeginForm
("ProcessForm","Home",FormMethod.Post))
{ 
  <table cellpadding="10" border="1">
    <tr>
      <td>@Html.LabelFor(m=>m.EmployeeID)</td>
      <td>
          @Html.TextBoxFor(m=>m.EmployeeID)
          @Html.ValidationMessageFor(m=>m.EmployeeID)
        </td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m => m.FirstName)</td>
        <td>
            @Html.TextBoxFor(m => m.FirstName)
            @Html.ValidationMessageFor(m => m.FirstName)
        </td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m => m.LastName)</td>
        <td>
            @Html.TextBoxFor(m => m.LastName)
            @Html.ValidationMessageFor(m => m.LastName)
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="submit" value="Submit" />
        </td>
   </tr>
 </table>
}
@Html.ValidationSummary()
...
...

So far so good. If you run the application at this point in time you will see this:

image

That's because ASP.NET MVC by default HTML encodes the output of validation helpers. So, your embedded HTML fragment is not treated as actual HTML and no image is displayed.

Now let's fix this problem with a bit of clever code.

Modify all your ValidationMessageFor() calls like this:

@Html.Raw(
HttpUtility.HtmlDecode(
@Html.ValidationMessageFor(m=>m.EmployeeID).ToHtmlString()
))

Notice the above like of code carefully. The return value of ValidationMessageFor() is MvcHtmlString. Calling ToHtmlString() method on it gives you the HTML encoded plain string. This HTML encoded plain string is then decoded using HtmlDecode() method of HttpUtility object. Doing so will give us raw HTML string. This string is finally sent to the response stream using Raw() helper.

Do the same for ValidationSummary() helper:

@Html.Raw(
HttpUtility.HtmlDecode(
@Html.ValidationSummary().ToHtmlString()
))

If you run the application now, you will see the validation errors being displayed as per Figure 2 of this article.

Using CSS class to display an image

In the above technique you embedded HTML fragment in the data annotation attributes. You may want to avoid this "ugly" way of specifying validation errors in some cases. If so, you can still achieve almost identical results using CSS classes.

Add a new metadata class or modify the existing one as shown below:

public class EmployeeMetadata
{
    [Required]
    [Range(1, int.MaxValue, 
     ErrorMessage = "Invalid EmployeeID!")]
    public int EmployeeID { get; set; }

    [Required]
    [StringLength(20, 
     ErrorMessage = "Invalid first name!")]
    public string FirstName { get; set; }

    [Required]
    [StringLength(20, 
      ErrorMessage = "Invalid last name!")]
    public string LastName { get; set; }
}

As you can see, you no longer embed HTML markup in the validation messages. Also revert back the Index view as it was before using the Raw() helper.

Next, add a CSS file and modify it to have the following style rules:

.field-validation-error {
    color: #b94a48;

    background-image: url(Images/error.png); 
    background-repeat:no-repeat;
    padding-left:22px;
    background-size:contain;
}

.validation-summary-errors {
    color: #b94a48;

    background-image: url(Images/error.png); 
    background-repeat:no-repeat;
    padding-left:44px;
    background-size: contain;
}

Remember that these CSS class names are assumed by the validation helpers and hence you should keep them exactly as shown above. The field-validation-error CSS class is applied to ValidationMessageFor() helpers whenever there is any validation error. SImilarly, validation-summary-errors CSS class is applied to ValidationSummary() whenever there is any validation error. Notice the style rules shown in bold letters. You are using a background image for the field level validations and the validation summary. You may need to adjust the padding as per your image dimensions. Note that there will be minor differences between the way images are shown in the previous technique and this technique. For example, in this technique ValidationSummary() will display just a single instance image for all the messages instead of one image per message (as in the previous technique).

That's it! Run the application and see if images are shown as expected.

posted Oct 8, 2016 by Shivaranjini

  Promote This Article
Facebook Share Button Twitter Share Button LinkedIn Share Button


Related Articles

Recently while developing a sample application in ASP.NET MVC, I came across a peculiar situation. There was a LINQ to SQL class with several extensibility methods defined (OnXXXXChanging kind of methods where XXXX is the name of a property). The methods were performing some validation checks and in case of any violation were throwing exceptions. The LINQ to SQL class was working fine. However, at some places I wanted to display the validation errors (thrown by the class as exceptions) on the ASP.NET MVC views. That is where the problem began. No matter what exception you throw in the class the Validation Summary never displays the error message. This behavior is by design and is intended to hide sensitive exception details from the end user. In this specific, however, I wanted to reveal the exception message to the end user because all exceptions were basically validation errors and I was sure that they are not disclosing any sensitive system information. To overcome the problem I developed a custom action filter. The remainder of this article explains how the custom action filter works.

To understand the problem let's first reproduce the error by developing a sample LINQ to SQL class. Begin by creating a new ASP.NET MVC web application. Once created add a SQL database to it and create an Employee table in it. The schema of the Employee table is shown below:

image

Now add a new LINQ to SQL class to the web application and create the Employee LINQ to SQL class by dragging and dropping the Employees table from the Server Explorer onto the design surface of .dbml file.

image

Now, add a new class in the Models folder and modify it as shown below:

public partial class Employee
{
  partial void OnFirstNameChanging(string value)
  {
    if (value.Length < 3 || value.Length > 50)
    {
      throw new ValidationException("Invalid First Name.");
    }
  }
  partial void OnLastNameChanging(string value)
  {
    if (value.Length < 3 || value.Length > 50)
    {
      throw new ValidationException("Invalid Last Name.");
    }
  }
  ...
}

The code creates a partial class Employee and adds extensibility methods OnFirstNameChanging() and OnLastNameChanging() to it (you can add additional methods for other properties if you so wish). If there are any validation errors the methods simply throw ValidationException.

Add a controller named HomeController as shown below:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Index(Employee emp)
    {
        return View();
    }

}

The Index() action method simply renders the Index view. The Index view consists of three textboxes to enter FirstName, LastName and BirthDate and uses MVC Validations (see markup and screen capture below).

<% using (Html.BeginForm()) { %>
<%= Html.ValidationSummary()%>
<p>First Name :</p>
<%= Html.TextBoxFor(model => model.FirstName)%>
<%= Html.ValidationMessageFor(model => model.FirstName, "*")%>
<p>Last Name :</p>
<%= Html.TextBoxFor(model => model.LastName)%>
<%= Html.ValidationMessageFor(model => model.LastName, "*")%>
<p>Birth Date :</p>
<%= Html.TextBoxFor(model => model.BirthDate)%>
<%= Html.ValidationMessageFor(model => model.BirthDate, "*")%>
<p>
<input id="Submit1" type="submit" value="Submit" />
</p>
<%}%>

image

If you try entering invalid values in FirstName and LastName fields you will get errors as shown below:

image

Notice that though FirstName and LastName fields are showing error (*) there is no descriptive error message at all. The actual error messages ("Invalid First Name" and "Invalid Last Name") are suppressed by MVC framework for security reasons. However, since you are deliberately throwing these exceptions you know that showing them to the end user won't create any problem.

The solution is to create a custom action filter as shown below:

public class ShowExceptionDetailsInValidationSummary : FilterAttribute, IActionFilter
{
    public Type ExceptionType { get; set; }
    public string Keys { get; set; }
    public string ErrorMessage { get; set; }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Controller c = (Controller)filterContext.Controller;
        string[] keys = null;
        Dictionary<string, string> messages = new Dictionary<string, string>();

        if (Keys != null && Keys!=string.Empty)
        {
            keys=Keys.Split(',');
        }

        if (keys == null)
        {
            foreach (string key in c.ModelState.Keys)
            {
                foreach (ModelError err in c.ModelState[key].Errors)
                {
                    if (ExceptionType != null)
                    {
                        if (err.Exception.GetType().Equals(ExceptionType))
                        {
                            if (ErrorMessage == null || ErrorMessage == string.Empty)
                            {
                                messages.Add(key, err.Exception.Message);
                            }
                            else
                            {
                                messages.Add(key, ErrorMessage);
                            }
                        }
                    }
                    else
                    {
                        if (ErrorMessage == null || ErrorMessage == string.Empty)
                        {
                            messages.Add(key, err.Exception.Message);
                        }
                        else
                        {
                            messages.Add(key, ErrorMessage);
                        }
                    }
                }
            }
        }
        else
        {
            foreach (string key in keys)
            {
                if(c.ModelState.Keys.Contains(key))
                {
                    foreach (ModelError err in c.ModelState[key].Errors)
                    {
                        if (ExceptionType != null)
                        {
                            if (err.Exception.GetType().Equals(ExceptionType))
                            {
                                if (ErrorMessage == null || ErrorMessage == string.Empty)
                                {
                                    messages.Add(key, err.Exception.Message);
                                }
                                else
                                {
                                    messages.Add(key, ErrorMessage);
                                }
                            }
                        }
                        else
                        {
                            if (ErrorMessage == null || ErrorMessage == string.Empty)
                            {
                                messages.Add(key, err.Exception.Message);
                            }
                            else
                            {
                                messages.Add(key, ErrorMessage);
                            }
                        }
                    }
                }
            }
        }

        foreach (string key in messages.Keys)
        {
            c.ModelState.AddModelError(key, messages[key]);
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }

}

The ShowExceptionDetailsInValidationSummary action filter essentially loops through the Model Errors and then programmatically adds a model error message.

Once the ShowExceptionDetailsInValidationSummary action filter is ready you can decorate the Index() action method with it as shown below :

[HttpPost]
[ShowExceptionDetailsInValidationSummary]
public ActionResult Index(Employee emp)
{
    return View();
}

If you run the application again and try to enter invalid values for FirstName and LastName you will correctly get error messages as shown in the following figure.

image

By default ShowExceptionDetailsInValidationSummary action filter will display all the exception messages in the validation summary. You can also specify that only certain exceptions be displayed.

[ShowExceptionDetailsInValidationSummary(ExceptionType=typeof(ValidationException))]

Further you can also customize the error message and keys using ErrorMessage and Keys properties of ShowExceptionDetailsInValidationSummary class respectively.

READ MORE
...