Search This Blog

Friday, October 16, 2015

VB6 string Left, Right and Mid functions in C#

While working on migration code from VB6 to C#, we encounter that the in-build string functions which were available in VB6 but are not available in C#. For ex – Left, Mid and Right functions.
One possible solution is to use Substring method however it has its own caveat. Substring method works until the provided string has a known length; and It throws exception if the length is unknown (while VB6 does not raise any exception).

you can use the below string extensions method to achieve VB equivalent functionality:
    public static class StringExtensions
    {
        /// <summary>
        /// Retrieves a substring from this instance. The substring starts at a specified length from right.
        /// </summary>
        /// <param name="self">The self instance.</param>
        /// <param name="length">The number of characters in the substring.</param>
        /// <returns>
        /// The substring starts at a specified length from right.
        /// </returns>
        public static string Right(this string self, int length)
        {
            if (string.IsNullOrEmpty(self))
            {
                return string.Empty;
            }
            else if (length >= self.Length)
            {
                return self;
            }
            else
            {
                return self.Substring(self.Length - length);
            }
        }

        /// <summary>
        /// Retrieves a substring from this instance. The substring starts at a specified length from left.
        /// </summary>
        /// <param name="self">The self instance.</param>
        /// <param name="length">The number of characters in the substring.</param>
        /// <returns>
        /// The substring starts at a specified length from left
        /// </returns>
        public static string Left(this string self, int length)
        {
            if (string.IsNullOrEmpty(self))
            {
                return string.Empty;
            }
            else if (length >= self.Length)
            {
                return self;
            }
            else
            {
                return self.Substring(0, length);
            }
        }

        /// <summary>
        /// Retrieves a substring from this instance. The substring starts at a specified position.
        /// </summary>
        /// <param name="self">The self instance.</param>
        /// <param name="start">The zero-based starting character position of a substring in this instance.</param>
        /// <returns>
        /// The substring starts at a specified position to length of instance.
        /// </returns>
        public static string Mid(this string self, int start)
        {
            if (string.IsNullOrEmpty(self))
            {
                return string.Empty;
            }

            return Mid(self, start, self.Length);
        }

        /// <summary>
        /// Retrieves a substring from this instance. The substring starts at a specified position to provided length.
        /// </summary>
        /// <param name="self">The self instance.</param>
        /// <param name="start">The zero-based starting character position of a substring in this instance.</param>
        /// <param name="length">The number of characters in the substring.</param>
        /// <returns>
        /// The substring starts at a specified position to length of instance to provided length.
        /// </returns>
        public static string Mid(this string self, int start, int length)
        {
            if (string.IsNullOrEmpty(self))
            {
                return string.Empty;
            }
            else if (start > self.Length)
            {
                return string.Empty;
            }
            else if (length >= (self.Length - start))
            {
                // if no. of characters requested for is more than length available
                return self.Substring(start);
            }
            else
            {
                return self.Substring(start, length);
            }
        }
    }

You can read about other equivalent methods at here.

Thursday, October 15, 2015

MVC Date range validation with data annotations

Is it possible to check if a date is less/more than or equal to the current date?
Yes, you can use the below custom date range validation which compares the date value to provided min and max date or mentioned dependency properties. It also demonstrates the client side validation support and integration.

CustomDateRange Attribute 


 public enum CustomDateRangeType
    {
        /// <summary>
        /// The direct value of property.
        /// </summary>
        Value,

        /// <summary>
        /// The dependent property.
        /// </summary>
        DependentProperty
    }


namespace CustomDateRange.ValidationAttributes
{
    using Constants;
    using Models;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using System.Web.Mvc;

    /// <summary>
    /// The CustomDateComparAttribute Validator
    /// </summary>
    [AttributeUsage(AttributeTargets.All | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class CustomDateRangeAttribute : ValidationAttribute, IClientValidatable
    {

        private const string UniversalDatePattern = "yyyy-M-d";

        /// <summary>
        /// The min date.
        /// </summary>
        private string minDate;

        /// <summary>
        /// The max date.
        /// </summary>
        private string maxDate;

        /// <summary>
        /// The date range type
        /// </summary>
        private CustomDateRangeType dateRangeType;

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomDateRangeAttribute"/> class.
        /// </summary>
        /// <param name="minDate">
        /// The min date in <example>yyyy-M-d</example> format. Throws FormatException exception if not provided in specified format.
        /// </param>
        /// <param name="maxDate">
        /// max date in <example>yyyy-M-d</example> format. Throws FormatException exception if not provided in specified format.
        /// </param>
        public CustomDateRangeAttribute(string minDate, string maxDate)
            : this(CustomDateRangeType.Value, minDate, maxDate)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomDateRangeAttribute" /> class.
        /// </summary>
        /// <param name="dateRangeType">Type of the date range.</param>
        /// <param name="minDate">The minimum date dependent property or value. If value then it should be <example>yyyy-M-d</example> format.</param>
        /// <param name="maxDate">The maximum date property or value. If value then it should be <example>yyyy-M-d</example> format.</param>
        public CustomDateRangeAttribute(CustomDateRangeType dateRangeType, string minDate, string maxDate)
        {
            if (dateRangeType == CustomDateRangeType.Value)
            {
                if (!IsValidDate(minDate))
                {
                    throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Max date should be in {0} format.", UniversalDatePattern));
                }

                if (!IsValidDate(maxDate))
                {
                    throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Min date should be in {0} format.", UniversalDatePattern));
                }
            }

            this.dateRangeType = dateRangeType;
            this.minDate = minDate;
            this.maxDate = maxDate;
        }

        /// <summary>
        /// Gets the min date.
        /// </summary>
        public string MinDate
        {
            get
            {
                return this.minDate;
            }
        }

        /// <summary>
        /// Gets the max date.
        /// </summary>
        public string MaxDate
        {
            get
            {
                return this.maxDate;
            }
        }

        /// <summary>
        /// Gets the type of the date range.
        /// </summary>
        /// <value>
        /// The type of the date range.
        /// </value>
        public CustomDateRangeType DateRangeType
        {
            get
            {
                return this.dateRangeType;
            }
        }

        /// <summary>
        /// gets client validation rules
        /// </summary>
        /// <param name="metadata">
        /// meta data parameter
        /// </param>
        /// <param name="context">
        /// controller context
        /// </param>
        /// <returns>
        /// client validation rule
        /// </returns>
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
            ModelMetadata metadata,
            ControllerContext context)
        {
            if (metadata != null)
            {
                return new[]
                           {
                               new ModelClientValidationCustomDateRangeRule(
                                   this.ErrorMessageString,
                                   this.DateRangeType,
                                   this.MinDate,
                                   metadata.PropertyName,
                                   this.MaxDate)
                           };
            }

            return null;
        }

        /// <summary>
        /// overridden method
        /// </summary>
        /// <param name="value">
        /// value to be compared
        /// </param>
        /// <param name="validationContext">
        /// validation context
        /// </param>
        /// <returns>
        /// validation result
        /// </returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var result = ValidationResult.Success;
            var errorResult = new ValidationResult(this.ErrorMessageString);
            if (value == null)
            {
                return result;
            }

            DateTime dateValue = (DateTime)value;

            if (this.DateRangeType == CustomDateRangeType.Value)
            {
                if (ParseDate(this.MinDate) <= dateValue && dateValue <= ParseDate(this.MaxDate))
                {
                    return result;
                }
            }
            else
            {
                if (validationContext == null || string.IsNullOrEmpty(this.MinDate) || string.IsNullOrEmpty(this.MaxDate))
                {
                    return errorResult;
                }

                var minDatePropertyInfo = validationContext.ObjectType.GetProperty(this.MinDate);
                var maxDatePropertyInfo = validationContext.ObjectType.GetProperty(this.MaxDate);
                if (minDatePropertyInfo == null || maxDatePropertyInfo == null)
                {
                    return errorResult;
                }

                var minDateValue = Convert.ToDateTime(
                    minDatePropertyInfo.GetValue(validationContext.ObjectInstance, null), 
                    CultureInfo.CurrentCulture);
                var maxDateValue = Convert.ToDateTime(maxDatePropertyInfo.GetValue(validationContext.ObjectInstance, null), 
                    CultureInfo.CurrentCulture);

                if (minDateValue <= dateValue && dateValue <= maxDateValue)
                {
                    return result;
                }
            }

            return errorResult;
        }

        /// <summary>
        /// The parse date.
        /// </summary>
        /// <param name="dateValue">
        /// The date value.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        private static DateTime ParseDate(string dateValue)
        {
            return DateTime.ParseExact(
                dateValue,UniversalDatePattern, 
                CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// The is valid date.
        /// </summary>
        /// <param name="dateValue">
        /// The date value.
        /// </param>
        /// <returns>
        /// A value indicating whether the provided dateValue is a valid date.
        /// </returns>
        private static bool IsValidDate(string dateValue)
        {
            DateTime? date = null;
            var regex = new Regex(@"\d{4}-\d{1,2}-\d{1,2}");
            if (regex.IsMatch(dateValue))
            {
                var dateParts = dateValue.Split('-');
                if (dateParts.Length == 3)
                {
                    date = new DateTime(
                        Convert.ToInt32(dateParts[0], CultureInfo.InvariantCulture),
                        Convert.ToInt32(dateParts[1], CultureInfo.InvariantCulture),
                        Convert.ToInt32(dateParts[2], CultureInfo.InvariantCulture));
                }
            }

            return date != null;
        }

        /// <summary>
        ///     ModelClientValidationCustomCompareRule class
        /// </summary>
        private class ModelClientValidationCustomDateRangeRule : ModelClientValidationRule
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="ModelClientValidationCustomDateRangeRule"/> class.
            /// </summary>
            /// <param name="errorMessage">error message</param>
            /// <param name="dateRangeType">Type of the date range.</param>
            /// <param name="minDateProperty">The minimum date property.</param>
            /// <param name="currentProperty">The current property.</param>
            /// <param name="maxDateProperty">The maximum date property.</param>
            public ModelClientValidationCustomDateRangeRule(
                string errorMessage,
                CustomDateRangeType dateRangeType,
                string minDateProperty,
                string currentProperty,
                string maxDateProperty)
            {
                this.ErrorMessage = errorMessage;
                this.ValidationType = "customdaterange";
                this.ValidationParameters.Add("daterangetypeproperty", dateRangeType.ToString());
                this.ValidationParameters.Add("mindateproperty", minDateProperty);
                this.ValidationParameters.Add("currentproperty", currentProperty);
                this.ValidationParameters.Add("maxdateproperty", maxDateProperty);
            }
        }
    }
}

The function IsValid validates the provided range.

Client side integration

Following code registers the "customdaterange" adapter and its validator method to jQuery validation framework.

(function ($) {
    jQuery.validator.addMethod('customdaterange', function (value, element, param) {
        if (value == '' || value == undefined) {
            return true;
        }

        var minValue;
        var maxValue;

        if (param.daterangetypeproperty == "DependentProperty") {
            var minDateValue = $('#' + param.mindateproperty).val();
            var maxDateValue = $('#' + param.maxdateproperty).val();
            minValue = new Date(minDateValue);
            maxValue = new Date(maxDateValue);
        } else {
            minValue = new Date(param.mindateproperty);
            maxValue = new Date(param.maxdateproperty);
        }

        var currentValue = new Date(value);
        if (minValue <= currentValue && currentValue <= maxValue) {
            return true;
        }

        return false;
    });

    jQuery.validator.unobtrusive.adapters.add('customdaterange', ['daterangetypeproperty', 'mindateproperty', 'currentproperty', 'maxdateproperty'], function (options) {
        var params = {
            daterangetypeproperty: options.params.daterangetypeproperty,
            mindateproperty: options.params.mindateproperty,
            currentproperty: options.params.currentproperty,
            maxdateproperty: options.params.maxdateproperty
        };

        options.rules['customdaterange'] = params;
        if (options.message) {
            options.messages['customdaterange'] = options.message;
        }
    });
}(jQuery));

Usage 

Model

For demo purpose, I have created following class. The attached attribute "CustomDateRange" applies the validator over the associated properties. The property "DateCompareWithMinMaxValue" demonstrates usage of the const min and max value (Note, the format is yyyy-MM-dd) And  DateCompareWithMinMaxDependentProperty demonstrates usage of the min and max dependent properties example.

public class DateRangeModel
    {
        public DateRangeModel()
        {
            this.MinDateDependentProperty = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
            this.MaxDateDependentProperty = DateTime.Today.AddDays(1 - DateTime.Today.Day).AddMonths(1);
        }

        [Required]
        [CustomDateRange("2015-10-01", "2015-10-15", ErrorMessage = "Date value is not in range.")]
        [DataType(DataType.Date)]
        public DateTime DateCompareWithMinMaxValue { get; set; }

        [Required]
        [CustomDateRange(CustomDateRangeType.DependentProperty, "MinDateDependentProperty", "MaxDateDependentProperty", 
            ErrorMessage = "Date to select value is not in range.")]
        [DataType(DataType.Date)]
        public DateTime DateCompareWithMinMaxDependentProperty { get; set; }

        [Required]
        [DataType(DataType.Date)]
        public DateTime MinDateDependentProperty { get; set; }

        [Required]
        [DataType(DataType.Date)]
        public DateTime MaxDateDependentProperty { get; set; }
    }


Demo

For demoing the the usage, I have created the following view. The first section demonstrates the usage of constant min and max value range and second dependent properties range.


When date value is out of range then date controls are validated 


The complete demo application can be downloaded from here.