Introduction
This is a simple tip to let you handle the data and time scenarios working in MVC. Suppose you have a scenario in which you take date input from user. Let's say Date of Birth. Now let's say user enters 10.-11-1995, Now how will you or the code determine which is day and which is month in the input. So it can be a serious problem that must be handled with care.
The root of the problem is .NET framework will always assume that the incoming date is according to the current culture. But when you are building multi-culture website, the problem becomes bigger. Let's see how we can standardize such things in MVC.
Background
There is not much background or pre-requisites to start with this tip. This includes a very basic knowledge of MVC. I expect that you are aware of how you bind data from controller to the views using Models. In case you are very new to MVC, you can take a look at the following article first:
Secondly, I hope you are familiar with Inheritance concept of OOPS. You can override the default behaviours of Interface/Class methods by inheritance. This is all about the background. Now, let's start.
Using the Code
To have a better understanding, you can follow the steps resulting in an application with Independent DateFormat
architecture.
- First of all, create an MVC Empty application.
- Create a Model Names Employee with property as follows:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.Date), DisplayFormat(DataFormatString = @"{0:dd\/MM\/yyyy HH:mm}",
ApplyFormatInEditMode = true)]
public DateTime DOB { get; set; }
[DataType(DataType.Date), DisplayFormat(DataFormatString = @"{0:dd\/MM\/yyyy HH:mm}",
ApplyFormatInEditMode = true)]
public DateTime JoiningDate { get; set; }
}
- Create a Home Controller and create a view for
Index
method. The index view we will use to add the new Employee
record. Hence, make the view of type 'Employee
'. Following is the HTML code for my Index.cshtm page.
@{
Layout = null;
}
@model MVCDateTimeDemo.Models.Employee
<!DOCTYPE HTML>
<html>
<head>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css"
rel="stylesheet">
<link rel="stylesheet" type="text/css" media="screen"
href="http://tarruda.github.com/bootstrap-datetimepicker/assets/css/bootstrap-datetimepicker.min.css">
</head>
<body>
@using (Html.BeginForm("SaveEmployee", "Home", FormMethod.Post,
new { enctype = "multipart/form-data", id = "form1" }))
{
<div>
<table>
@*@foreach (var employee in Model.Employees)
{
<tr><td>@employee.FirstName</td><td>@employee.LastName</td>
<td>@employee.DOB.ToString("dd/MM/yyyy")</td>
<td>@employee.JoiningDate.ToString("dd/MM/yyyy")</td></tr>
}*@
</table>
<br />
@Html.HiddenFor(m => m.Id)
<br />
@Html.TextBoxFor(m => m.FirstName, new { placeholder = "First name" })
<br />
@Html.TextBoxFor(m => m.LastName, new { placeholder = "Last Name" })
<br />
<div id="datetimepicker1" class="input-append date">
@Html.TextBoxFor(m => m.DOB, "{0:dd/MM/yyyy HH:mm}",
new { placeholder = "Date Of Birth", @class = "dtPicket" })
<span class="add-on"><i data-time-icon="icon-time"
data-date-icon="icon-calendar"></i>
</span>
</div>
<br />
<div id="datetimepicker2" class="input-append date">
@Html.TextBoxFor(m => m.JoiningDate, "{0:dd/MM/yyyy HH:mm}",
new { placeholder = "Joining Date", @class = "dtPicket" })
<span class="add-on"><i data-time-icon="icon-time"
data-date-icon="icon-calendar"></i>
</span>
</div>
<br />
<input type="submit" value="Submit" />
</div>
<script type="text/javascript"
src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js">
</script>
<script type="text/javascript"
src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/js/bootstrap.min.js">
</script>
<script type="text/javascript"
src="http://tarruda.github.com/bootstrap-datetimepicker/assets/js/bootstrap-datetimepicker.min.js">
</script>
<script type="text/javascript"
src="http://tarruda.github.com/bootstrap-datetimepicker/assets/js/bootstrap-datetimepicker.pt-BR.js">
</script>
<script type="text/javascript">
$('#datetimepicker1, #datetimepicker2').datetimepicker({
format: 'dd/MM/yyyy HH:mm'
});
</script>
}
</body>
<html>
- Now add an action to the Home controller that will handle the post request of the above page. As you can see, I have added '
SaveEmployee
' action to Form
attribute. - Create a view to display the records.
- You can use database or can create a method that returns List of employees temporarily binding the data.
Now, we are ready with application to Save and display the Employees. Just build and run the project
Problem
Enter the date informat dd/MM/yyyy, let's say 25/10/1990 00:00:00. You expect the same value at controller level. But when you debug the code, you will see "01/01/001 00:00:00".
Why So?: This is because the .NET Framework will consider that the input value is the same culture the machine has on which your code is hosted until you explicitly mention the culture in your web.config file. But what if you have multiple languages in you webiste. Then, every language has its own culture and every culture has a different (almost) date frmat. Hence, the problem bounced back at high risk this time.
But you have specified the DataAnnotation DisplayFormat attribute:
You will be thinking here that you are getting this issue despite mentioning the display format in Model data annotation attributes. Why is it not applied ??
Solution: The data annotation is actually applied but what actually annotation does is to validate the data before saving. Now as per user, 25/10/1990 is a valid date but not for .NET MVC Compiler. Now to force compiler to pass the input as per your specified format is to override the default Model Binder. So to do that, please see the following steps carefully:
Step 1
Code for overriding the DefaultModelBinder
.
Following is the code that needs to be added to Global file.
As you can see above, in the Application_Start
method of Global file, I have added an additional line than your Global file has:
ModelBinders.Binders.Add(typeof(DateTime), new CustomDateModelBinder());
This line says, you are overriding the default model finder for model
property of type DateTime.
Step 2
Now let's write the code for our CustomeDataModelBinder
.
public class CustomDateModelBinder : DefaultModelBinder
{
public override object BindModel
(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (!string.IsNullOrEmpty(displayFormat) && value != null)
{
DateTime date;
displayFormat = displayFormat.Replace
("{0:", string.Empty).Replace("}", string.Empty);
if (DateTime.TryParseExact(value.AttemptedValue, displayFormat,
CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
return date;
}
else
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName,
string.Format("{0} is an invalid date format", value.AttemptedValue)
);
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
Make sure this code should be written to Global file only as DefaultModelBinder
context is within Global file.
Now let's have an overview of our file system so that we can have clear view of what files I have added.
Step 3
Build and run the code.
Now try to run your code and you will see that the Date format is working in the right way you wanted.
Points of Interest
- Custom Model Binder overrides the default behaviour of MVC Model Binder by overriding the system culture.
- Display custom message for validation error for model property.
- Here, I have added the
customModelBinder
for Model
property of type DateTime
only. In case you want to do the same for Decimal
/Int
, etc., just add the entry for respective Application_Start
action and you are done. No need to re-write Custom Override method.