A flexible Default Value for your DateTime properties

A flexible Default Value for your DateTime properties

When creating an MVC application with Entity Framework, it is possible to set default values for most properties in a model using the DefaultValue attribute. However, no much flexibility is offered for a DateTime property. This article presents a custom validation attribute for DateTime types that accepts different formats for defining the default value of the property.

 

About Default Values

Let’s start defining the problem that this article wants to address. The context is an ASP.NET MVC application with a model, say a Blog Post (class BlogPost) that has a PublishOn property of DateTime type. I want to valorise this property with a predefined value if the user does not enter any value via the user interface. Typically, I would set the default value of a model property as a DefaultValue attribute, as in:

[DefaultValue(DateTime.Now)]

public DateTime PublishOn { get; set; }

 

However, this code does not compile because an attribute argument must be a constant expression evaluated at compile time, and DateTime.Now is clearly evaluated at run time instead.

 

In light of this, I need another approach to define constant expressions that can be used for identifying a default value for a date, and still be able to be evaluated at run time. The approach that we are going to take in based on string expressions in different formats, to cover multiple options.

 

An attribute argument must be a constant expression.

 

The DefaultDateTimeValue Attribute

A new attribute is required. Let’s call it DefaultDateTimeValue because it will deal with DateTime types only. But I want it to be flexible enough to accept:

1.     Evaluation of DateTime.Now or DateTime.Today at run time;

2.     Definition of an absolute date, such as 01/03/2016;

3.     Definition of a relative date, such as “in 30 days from today”;

4.     Evaluation of expressions like “first of year, last of month”, etc.

 

To express this in C# code, I would like to be able to declare something like:

// Evaluate to the current date at run time

[DefaultDateTimeValue("Now")]

public DateTime? PublishOn { get; set; }

 

// Define an absolute date

[DefaultDateTimeValue("01/03/2016")]

public DateTime? PublishOn { get; set; }

 

// Define a relative date (30 days from now)

[DefaultDateTimeValue("30.00:00:00")]

public DateTime? PublishOn { get; set; }

 

// Define a relative date (1 hour from now)

[DefaultDateTimeValue("1:00:00")]

public DateTime? PublishOn { get; set; }

 

// Evaluate to last date of the month

[DefaultDateTimeValue("LastOfMonth")]

public DateTime? PublishOn { get; set; }

 

Exciting! Let’s go in order: first we need our custom attribute, and we make it inheriting from ValidationAttribute in the System.ComponentModel.DataAnnotations namespace because we want our MVC application to validate this attribute at run time.

[AttributeUsage(AttributeTargets.Property)]

public sealed class DefaultDateTimeValueAttribute : ValidationAttribute

{

    public string DefaultValue { get; set; }

 

    public DefaultDateTimeValueAttribute(string defaultValue)

    {

        DefaultValue = defaultValue;

    }

 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)

    {

        PropertyInfo property = validationContext.ObjectType.GetProperty(validationContext.MemberName);

 

        // Set default value only if no value is already specified

        if (value == null)

        {

            DateTime defaultValue = GetDefaultValue();

            property.SetValue(validationContext.ObjectInstance, defaultValue);

        }

 

        return ValidationResult.Success;

    }

DefaultDateTimeValueAttribute exposes a DefaultValue property, which is assigned in the constructor, and overrides IsValid. This method is invoked during the model validation for the specific property, passing the entered value and a validation context. From the validation context, we obtain the object type (ObjectType) where the property is defined, which is our model, and the property name (MemberName), in our example that is “PublishOn”.

If there is no value entered by the user, then evaluate the default value to assign to the property. Validation is always successful because a default value will be assigned in any case.

 

Different Default Date Formats

Evaluation of the default value to assign to the property is done in the GetDefaultValue method of the custom attribute. This method handles the four different options for DateTime values that is possible to specify.

private DateTime GetDefaultValue()

{

    // Resolve a named property of DateTime, like "Now"

    if (this.IsProperty)

    {

        return GetPropertyValue();

    }

 

    // Resolve a named extension method of DateTime, like "LastOfMonth"

    if (this.IsExtensionMethod)

    {

        return GetExtensionMethodValue();

    }

 

    // Parse a relative date

    if (this.IsRelativeValue)

    {

        return GetRelativeValue();

    }

 

    // Parse an absolute date

    return GetAbsoluteValue();

}

 

private bool IsProperty

    => typeof(DateTime).GetProperties()

        .Select(p => p.Name).Contains(this.DefaultValue);

 

private bool IsExtensionMethod

    => typeof(DefaultDateTimeValueAttribute).Assembly

        .GetType(typeof(DateTimeExtensions).FullName)

        .GetMethods()

        .Where(m => m.IsDefined(typeof(ExtensionAttribute), false))

        .Select(p => p.Name).Contains(this.DefaultValue);

 

private bool IsRelativeValue

    => this.DefaultValue.Contains(":");

 

The first requirement for evaluating DateTime.Now at run time is met by the GetPropertyValue method. If the specified default value, which is a string, matches the name of a property of the DateTime object, such as “Now” or “Today”, the GetPropertyValue method is called (more about this method later).

If the string expression matches the name of an extension method to the DateTime object, that we have defined separately, then the GetExtensionMethodValue method is invoked (more about extensions soon, be patient…).

Carrying on with the options, if the string expression contains a colon punctuation mark “:”, then we are likely in presence of an expression of a relative date, so the GetRelativeValue method is invoked. More about why the colon sign in a moment.

Otherwise, we assume it is an absolute date and the GetAbsoluteValue method is finally invoked.

Sounds logic, but let’s clarify a few things here.

To determine whether the string expression set as expected default value is a property of the DateTime object is a simple reflection on the DateTime type, getting the name of all properties and checking whether this list contains the expression (“Now” for example).

For determining whether the expression matches the name of an extension method, instead, we need to go a bit around with our reflection, as extension methods are not defined directly into the object type. So we need to check for existence of the extension class in the current assembly, retrieve a list of all methods defined as extensions (they have the ExtensionAttribute silently added by the compiler), and match the name against the default value expression.

About relative dates we have already said that the check is simply for a “:” sign. This is because, as we will see later, the relative expression uses the TimeSpan format, which is “dd.hh:mm:ss” for days, hours, minutes and seconds, and “hh:mm:ss” for hours, minutes and seconds. Yes ok, a regular expression match would be more robust, I know… I leave it for a future refactoring J

Now that we know how to differentiate the date formats, let’s look at the implementation of the method to evaluate these expressions at run time.

 

Evaluating the Default Value

GetPropertyValue

GetPropertyValue is the method for evaluating a property of DateTime at run time. After creating an instance of a DateTime object via the Activator object, we simply obtain the value of the property by reflection.

private DateTime GetPropertyValue()

{

    var instance = Activator.CreateInstance<DateTime>();

    var value = (DateTime)instance.GetType()

        .GetProperty(this.DefaultValue)

        .GetValue(instance);

 

    return value;

}

 

GetExtensionMethodValue

GetExtensionMethodValue takes a longer route to execute an extension method. This is because extension methods cannot be found directly on the type being reflected, as they are indeed extensions, that is defined inside another object. So, the approach is to find the extension object in the current assembly, get the method by name and then invoke it on an instance of DateTime obtained via Activator. As we are using the extensions methods to calculate dates relative to the current date, e.g. the first of the year, the last of the month, etc., we pass DateTime.Now as parameter.

private DateTime GetExtensionMethodValue()

{

    var instance = Activator.CreateInstance<DateTime>();

    var value = (DateTime)typeof(DefaultDateTimeValueAttribute).Assembly

        .GetType(typeof(DateTimeExtensions).FullName)

        .GetMethod(this.DefaultValue)

        .Invoke(instance, new object[] { DateTime.Now });

 

    return value;

}

 

GetRelativeValue

GetRelativeValue works with time spans. The format of the default value is what can be obtained by converting a TimeSpan object to a string, and what can be recognised by the TimeSpan.TryParse method. The time span is then added to the current date to obtain the default date. Please note that time spans can be also negative, so to express a value like “30 days ago”, we should use the format “-30.00:00:00”.

private DateTime GetRelativeValue()

{

    TimeSpan timeSpan;

    if (!TimeSpan.TryParse(this.DefaultValue, out timeSpan))

    {

        return default(DateTime);

    }

 

    return DateTime.Now.Add(timeSpan);

}

 

GetAbsoluteValue

GetAbsoluteValue works with fixed dates, so the format is the typical combination of DD/MM/YYYY, depending on the computer’s locale. DateTime.TryParse is used to parse the string expression.

private DateTime GetAbsoluteValue()

{

    DateTime value;

    if (!DateTime.TryParse(this.DefaultValue, out value))

    {

        return default(DateTime);

    }

 

    return value;

}

 

The DateTime Extensions

I mentioned before to some extensions methods that enable some simple evaluation of dynamic dates at run time, that is dates that depends on the current date. The following methods have been implemented; feel free to add your own as needed:

·         FirstOfYear: returns the 1st of January of the current year;

·         LastOfYear: returns the 31st of December of the current year;

·         FirstOfMonth: returns the 1st day of the current month and year;

·         LastOfMonth: returns the last day of the current month and year, taking into consideration whether it’s a leap year (this is done automatically by the DateTime.DaysInMonth method).

public static class DateTimeExtensions

{

    public static DateTime FirstOfYear(this DateTime dateTime)

        => new DateTime(dateTime.Year, 1, 1, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

 

    public static DateTime LastOfYear(this DateTime dateTime)

        => new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

 

    public static DateTime FirstOfMonth(this DateTime dateTime)

        => new DateTime(dateTime.Year, dateTime.Month, 1, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

 

    public static DateTime LastOfMonth(this DateTime dateTime)

        => new DateTime(dateTime.Year, dateTime.Month, DateTime.DaysInMonth(dateTime.Year, dateTime.Month), dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

}

 

The MVC Application

We are now in the position of implementing all this infrastructure code into our MVC application. Sticking to a simple model that contains a DateTime property, we can try different combinations of default values as offered by our custom attribute. I left the generation of the Controller and relative Views to Visual Studio. There is nothing to change in the generated code, as all the “magic” happens in the model by setting the DefaultDateTimeValue attribute for the DateTime property.

 

The Model

The model is a pseudo blog post with simply a primary key (Id), a unique title and a date when the post is going to be published. Feel free to try different combinations of default values. Also, note that the PublishOn property is a nullable DateTime. This is because the jQuery client-side validation would prevent even submitting the form if no value is entered on the UI. If you are bothered by the nullability of the property, disable client-side validation and let the form being submitted. Validation will occur server-side during the model binding, which is when the validation attributes are checked. This included also our custom validation attribute that assigns a default value to the property.

 

public class BlogPost

{

    public int Id { get; set; }

 

    [StringLength(1000), Required, Index(IsUnique = true)]

    public string Title { get; set; }

 

    [DisplayName("Publish On")]

    [DefaultDateTimeValue("Now")]

    //[DefaultDateTimeValue("01/03/2016")]

    //[DefaultDateTimeValue("30.00:00:00")]

    //[DefaultDateTimeValue("1:00:00")]

    //[DefaultDateTimeValue("LastOfMonth")]

    public DateTime? PublishOn { get; set; }

}

 

The View

All the views are generated by the EF scaffolding capability of Visual Studio, so expect to see the typical Create, Delete, Details, Edit and Index Razor views. No changes are applied to these views, but in some way, we need to inform the Html helper that generates the HTML code in the view, to display the default value. Remember, we have implemented a validation attribute, so the default value to assign is evaluated only when validation occurs, which is during the model binding. Still, we want to visualise the default value in the view when a new model is created.

To do so, we simply need to customise the editor template for the DateTime type. The good news is that MVC has a very powerful convention-based approach where we can extend the framework by simply adding files in the right position with the right name. Editor templates are used when the @Html.EditorFor method is used on a model property. Specific templates can be specified to be used instead of the standard rendering, but this would require a change to the view, because the name of the template has to be specified as an input parameter. Alternatively, for changing the editor template of all properties in a specific type, a Razor partial view with the name of the type can be specified, and saved in the View>Shared>EditorTemplates folder. In our case, that would be DateTime.cshtml.

 

The DateTime Editor Template

The DateTime editor template displays the date property as a text box using the Html.TextBox extension. Variation of this can be implemented if you decide to use richer components, like jQuery UI DatePicker. What is important here is how the value is evaluated. Information about the value entered in the model is contained in the TemplateInfo property of the ViewData, more precisely the FormattedModelValue object. This is a generic object, that contains the value of the model property, if any. If no value has been entered yet, which is the case when a new model is being created, the FormattedModelValue is an empty string.

We need to differentiate between creation and editing of the model, as the same editor template is used in both cases. When editing an existing model, FormattedModelValue is the same type of the underlying property. So we can check whether FormattedModelValue is DateTime, and if so, we take the assigned value to pass and display in the text box.

Otherwise, we can assume that there is no value assigned, so we need to resolve the default value to assign. This is done by calling a static method on the DefaultDateTimeValue attribute for evaluating the expression of the default value set on the model property. As we are not in the model binding phase here, we need to inform which is the exact object (the model) and property to evaluate. This information is contained in the ViewData.ModelMetadata.ContainerType and ViewData.ModelMetadata.PropertyName objects respectively.

@using DefaultDateTimeValue.Models

 

@{

    DateTime value = ViewData.TemplateInfo.FormattedModelValue is DateTime ?

        (DateTime)ViewData.TemplateInfo.FormattedModelValue :

        DefaultDateTimeValueAttribute.GetDefaultValue(ViewData.ModelMetadata.ContainerType, ViewData.ModelMetadata.PropertyName);

}

 

@Html.TextBox(string.Empty, value, htmlAttributes: ViewData["htmlAttributes"])

 

The public GetDefaultValue method of the DefaultDateTimeValueAttribute class simply resolves the property and attribute info via reflection, and then internally evaluates the default value in the same way as seen before during validation, by calling the private GetDefaultValue method.

public static DateTime GetDefaultValue(Type objectType, string propertyName)

{

    var property = objectType.GetProperty(propertyName);

    var attribute = property.GetCustomAttributes(typeof(DefaultDateTimeValueAttribute), false)

        ?.Cast<DefaultDateTimeValueAttribute>()

        ?.FirstOrDefault();

 

    return attribute.GetDefaultValue();

}

 

The entire source code is in CodePlex, feel free to download and use/amend it at your wish. If you have any interesting enhancements to bring, please shout, will be glad to hear from you!

 


  Comments

 

 Source Code

 Related Content
Adding a Secured Geo-located Audit Trail
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Adding Social Sharing to a Web Site
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Best practices for mobile form design in SharePoint
Build effective SharePoint forms with Nintex that are accessible anywhere, at any time, and on any device. You built the workflows, you built the forms, now make them mobile.
Bring your “A” game to ESPC16
With just over 3 weeks to go to Europe's largest gathering of SharePoint & Office 365 professionals, take a look at these tips that will help you get the most out of ESPC16…
Building an MVC application for SharePoint
Learn how to write code to perform basic operations with the SharePoint 2013 .NET Framework client-side object model (CSOM), and build an ASP.NET MVC application that retrieves information from a SharePoint server.
CIO vs CTO
What are the synergies and differences of the roles of a Chief Information Officer and a Chief Technology Officer? An open conversation about two roles with one mission…
Coded UI test automation of MVC applications with Visual Studio
Whether you are a software developer, tester, administrator or analyst, this article can help you master different types of UI testing of an MVC application, by using Visual Studio for creating coded UI test suites that can be automated for continuous execution.
Converting GIS spatial coordinates
Different formats and standards exist for describing geographical coordinates in GIS systems and applications. This article explains how to convert between the most used formats, presenting a working library written in C#.
Creating mobile accessible forms in SharePoint
With the release of the Nintex Mobile apps, SharePoint users can now optimise their experience across popular mobile devices and platforms.
Define your Performance Testing strategy with Visual Studio
Performance Testing is an essential part of software testing, with the specific goal of determining how a system performs in terms of responsiveness and stability under a particular workload. In this series of posts we’ll define and execute a good strategy for testing performance of an application using Visual Studio.
Disserting about colliding GUIDs and the Big Bang Theory
Can you generate two identical GUIDs? Would the world end if two GUIDs collide? How long does it take to generate a duplicate GUID and would we be still here when the result is found?
GIS Location Services in Dynamics CRM
A design paper about implementing GIS-based services for a local Council in Dynamics CRM, structuring address data, and delivering location services in the form of WebAPI endpoints via an Enterprise Service Bus.
Group or Team?
All teams are groups but not all groups are teams. What defines a group and what a team? When do we need one over the other one?
How to Give Constructive Feedback
Learning to give and receive constructive feedback is an essential part of learning, growing, improving and achieving our goals.
Mirroring an iPad on your laptop
Have you ever wanted to see your iPhone or iPad on a larger screen? Play games, watch movies, demo apps or present to your computer from your iPhone or iPad. Reflector mirrors iOS devices on big screens wirelessly using iOS built-in AirPlay mirroring.
Mobilize your SharePoint workflows
Build workflow applications in SharePoint that can be accessed on mobile devices using the Nintex solution for business process mobilization.
Natural String Sorting
Have you ever desired to have in your code a way to order a sequence of strings in the same way as Windows does for files whose name contains a mix of letters and numbers? Natural string sorting is not natively supported in .NET but can be easily implemented by specialising a string comparer and adding a few extensions to the enumerable string collection.
Sales Effectiveness in Dynamics CRM with Azure IoT and Machine Learning - Part 1
How can an organisation optimise its sales channels and product targeting by building a 365-degree view of its customers in Dynamics CRM? The answer, and topic of this article, is with the help of Azure IoT and Machine Learning services!
Scaling Applications with Azure Redis and Machine Learning - Part 1
This article presents design best practices and code examples for implementing the Azure Redis Cache and tuning the performance of ASP.NET MVC applications, optimising cache hit ratio and reducing “miss rate” with smart algorithms processed by Machine Learning.
Software Development Management in 11 steps
What it takes to be a great Software Development Manager? I have been building software for the last 15 years and have collected a few stories and experiences to share. Some I have used as questions when interviewing candidates. In 11 points, this is my story to date.
SOLID SharePoint apps with MVC
Practical code examples of ASP.NET MVC applications that connect to a SharePoint Server and comply with the SOLID principles.
The art of outsourcing projects
Outsourcing may look financially attractive, but working with companies in far-off lands introduces challenges that, if not considered properly, can drive any project to failure. Let’s explore some common pitfalls when working with offshore partners and a common-sense approach to work around them.
The value of hashtags
Customers expect a modern approach to advertising. Digital advertising can leverage evolving technology to provide just-in-time, just-at-the-right-place promotions.
We don't need no Yoda's syntax
There is an urban myth in the programmers’ community that the so called “Yoda’s syntax” performs better when checking an object for nullity. Let's demystify it...