Conditional required validation or field mandatory depends on another field MVC 4

I have experienced this situation that when I need to make a field mandatory if the user has entered a value in another field (or a particular value for that field).

Here is my example, I have two radio buttons says “Do you have the purchase receipt?” with options “yes” or “no”. If the user has selected “yes”, I need them to specify the date of the purchase as well.

image

Now the headache is, I can’t make “Purchase Date” required field. Because if user selects “no”, they don’t need to enter the “Purchase Date”. After I did some research and lookup from the internet. I find this solution on StackOverflow. It has a few bugs and I fixed them and shared over here in my blog.

I created a RequiredIfAttribute,

public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
    protected RequiredAttribute _innerAttribute;

    public string DependentProperty { get; set; }
    public object TargetValue { get; set; }

    public bool AllowEmptyStrings
    {
        get
        {
            return _innerAttribute.AllowEmptyStrings;
        }
        set
        {
            _innerAttribute.AllowEmptyStrings = value;
        }
    }

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        _innerAttribute = new RequiredAttribute();
        DependentProperty = dependentProperty;
        TargetValue = targetValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // get a reference to the property this validation depends upon
        var containerType = validationContext.ObjectInstance.GetType();
        var field = containerType.GetProperty(DependentProperty);

        if (field != null)
        {
            // get the value of the dependent property
            var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
            // trim spaces of dependent value
            if (dependentValue != null && dependentValue is string)
            {
                dependentValue = (dependentValue as string).Trim();

                if (!AllowEmptyStrings && (dependentValue as string).Length == 0)
                {
                    dependentValue = null;
                }
            }

            // compare the value against the target value
            if ((dependentValue == null && TargetValue == null) ||
                (dependentValue != null && (TargetValue.Equals("*") || dependentValue.Equals(TargetValue))))
            {
                // match => means we should try validating this field
                if (!_innerAttribute.IsValid(value))
                    // validation failed - return an error
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
            }
        }

        return ValidationResult.Success;
    }

    public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "requiredif",
        };

        string depProp = BuildDependentPropertyId(metadata, context as ViewContext);

        // find the value on the control we depend on;
        // if it's a bool, format it javascript style 
        // (the default is True or False!)
        string targetValue = (TargetValue ?? "").ToString();
        if (TargetValue is bool)
            targetValue = targetValue.ToLower();

        rule.ValidationParameters.Add("dependentproperty", depProp);
        rule.ValidationParameters.Add("targetvalue", targetValue);

        yield return rule;
    }

    private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
    {
        // build the ID of the property
        string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
        // unfortunately this will have the name of the current field appended to the beginning,
        // because the TemplateInfo's context has had this fieldname appended to it. Instead, we
        // want to get the context as though it was one level higher (i.e. outside the current property,
        // which is the containing object, and hence the same level as the dependent property.
        var thisField = metadata.PropertyName + "_";
        if (depProp.StartsWith(thisField))
            // strip it off again
            depProp = depProp.Substring(thisField.Length);
        return depProp;
    }
}

2. Create js validation and js unobtrusive validation, (I put them in document.ready() callback)

$.validator.addMethod('requiredif',
    function (value, element, parameters) {
        var id = '#' + parameters['dependentproperty'];

        // get the target value (as a string, 
        // as that's what actual value will be)
        var targetvalue = parameters['targetvalue'];
        targetvalue = (targetvalue == null ? '' : targetvalue).toString();

        // get the actual value of the target control
        // note - this probably needs to cater for more 
        // control types, e.g. radios
        var control = $(id);
        var controltype = control.attr('type');
        var actualvalue =
            (controltype === 'checkbox' ||  controltype === 'radio')  ?
            control.attr('checked').toString() :
            control.val();

        // if the condition is true, reuse the existing 
        // required field validator functionality
        if ($.trim(targetvalue) === $.trim(actualvalue) || ($.trim(targetvalue) === '*' && $.trim(actualvalue) !== ''))
            return $.validator.methods.required.call(
              this, value, element, parameters);

        return true;
    });

$.validator.unobtrusive.adapters.add(
    'requiredif',
    ['dependentproperty', 'targetvalue'],
    function (options) {
        options.rules['requiredif'] = {
            dependentproperty: options.params['dependentproperty'],
            targetvalue: options.params['targetvalue']
        };
        options.messages['requiredif'] = options.message;
    });

3. For the Model,

        [Required]
        public bool HasReceipt { get; set; }

         [RequiredIf("HasReceipt", true, ErrorMessage = "You must enter purchase date")]
        [Display(Name="Purchase Date")]
        public DateTime? PurchaseDate { get; set; }

4. When reference this validation js, I notice that it only works before the unobtrusive javascript,


    <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.requiredif.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

5. Now you have your conditional required validations.

There are many scenarios like this,

e.g.

If you have entered an Address field, you must enter Suburb, City.

If you have selected yes for a credit card, you must enter the credit card digits.

If you have subscribed a service, you must enter a valid email.

And many more.

Advertisements

Ninject with custom attribute and filter

I am implementing a custom credit check filter for a controller action. I use Ninject as my Ioc container. First, create a action filter and implement IActionFilter interface, implement the OnActionExecuted() and OnActionExecuting() methods. Within the credit checking functions, it needs a CreditService to check against the repository and database. So I have created the parameterized constructor using Ninject to inject the CreditService object.

 public class CreditCheckFilter : IActionFilter
    {
        private ICreditService _creditService { get; set; }

        public CreditCheckFilter(ICreditService myService)
        {
            _creditService = myService;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {

        }

        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var userName = filterContext.HttpContext.User.Identity.Name;
            var bal = _creditService.GetTokenBalance(WebSecurity.GetUserId(userName));
            if (bal <= 0)
            {
                RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
                redirectTargetDictionary.Add("action", "GetTokens");
                redirectTargetDictionary.Add("controller", "Account");
                filterContext.Result = new RedirectToRouteResult(redirectTargetDictionary);
               
            }
        }
    }

Now Let’s create a CreditCheckAttribute that implements FilterAttribute, this is the attribute we put against the controller action,

    public class CreditCheckAttribute : FilterAttribute { }

Now, we need to use Ninject BindFilter() method to bind our CreditCheckFilter, when a controller action has the attribute CreditCheckAttribute. In the ninject kernel, do this,

            kernel.BindFilter(FilterScope.Action, 0).WhenActionMethodHas();

The last step is to put CreditCheckAttribute() against the controller action,

        [CreditCheckAttribute]
        public ActionResult Playlist(TestViewModel model)
        {
            return View(model);
        }

Use Enum & Description to create select list item in MVC

There are always situations that we make good use of enums in projects. I like to use an Enum values to make my select list to save select-list-creation time. Here is what I always do,

Create an Enum, Eg. MonthFilter

 public enum MonthFilter
    { 
        [Description("Last Month")]
        LastMonth = 1,
        [Description("Last 3 Months")]
        Last3Months = 3,
        [Description("Last 6 Months")]
        Last6Months = 6,
        [Description("Last 12 Months")]
        Last12Months = 12,
        [Description("Year on Year")]
        YearOnYear = -1
    }

And then create the SelectListItem using this Enum and its descriptions,

    var monthFilters = from MonthFilter n in Enum.GetValues(typeof(MonthFilter))
                     select new SelectListItem { 
                            Value = ((int)n).ToString(), 
                            Text = Enumerations.GetEnumDescription(n)
                     };

Last step is create the dropdown list, by passing in the “monthFilters” Select List Items,

                @Html.DropDownList("MonthFilterDropDown", monthFilters, "Select...", null)

ElmahR, realtime elmah error loggings

ElmahR aggregates all errors coming out from the configured applications.

image

Errors get to the dashboard in real time as they occur in monitored applications, and here below you have a ‘feed’ displaying all of them, the newest ones on top of the stack.

image

ElmahR allows the addition of ‘extra’ modules, which are the way to enrich the dashboards with additional pieces like statistics or graphs about received errors.

image

image

This is really cool stuff!

Create a MVC 3 web solution template – Part 3, Create a new solution using template

Create a new project from Visual Studio. Find your template from Visual C# templates.

image

It will ask you to configure to use IIS express

image

Because template contains no “packages” folders and the pre-installed nuget packages have no references. Project won’t compile.

Now right click solution and select “Enable Nuget Package Restore”

image

When you build your solution, packages will be downloaded and your nuget package references will be ok to resolve.

image

From time being, pre-installed nuget packages will have updates.

Right click Solution, select “Manage Nuget Packages for Solution…”

Select Updates Tab, to update packages

image

 

It is almost flawless, but….

You need to add project reference “Business”,”Data” to your “Web” project. And “Data” to your “Business” project.

———————————————————————

Download the sample vstemplate from http://mvc3projecttemplate.codeplex.com/

Create a MVC 3 web solution template – Part 2, Create VS template and grouping projects

From part 1, I have talked about my project template structure. Now I want to talk about how do I create the VS templates and group them into one solution.

Visual Studio only allow you to create a template from one single project. So we need to write some vstemplate scripts to group our projects all together into one solution. And save the below script as “Template.vstemplate”.

</pre>
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="ProjectGroup">
 <TemplateData>
 <Name>Web MVC 3 Template</Name>
 <Description>MVC 3 web application template</Description>
 <ProjectType>CSharp</ProjectType>
 <TemplateGroupID>MyApplication</TemplateGroupID>
 <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp>
 <SortOrder>1000</SortOrder>
 <CreateNewFolder>false</CreateNewFolder>
 <DefaultName>MVC3Application</DefaultName>
 <ProvideDefaultName>true</ProvideDefaultName>
 <LocationField>Enabled</LocationField>
 <Icon>__TemplateIcon.png</Icon>
 <PreviewImage>__PreviewImage.png</PreviewImage>
 <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
 <PromptForSaveOnCreation>true</PromptForSaveOnCreation>
 <RequiredFrameworkVersion>4.0</RequiredFrameworkVersion>
 </TemplateData>
 <TemplateContent>
 <ProjectCollection>
 <ProjectTemplateLink ProjectName="$safeprojectname$.Business">
 Business.Template\MyTemplate.vstemplate
 </ProjectTemplateLink>
 <ProjectTemplateLink ProjectName="$safeprojectname$.Web">
 Web.Template\MyTemplate.vstemplate
 </ProjectTemplateLink>
 <ProjectTemplateLink ProjectName="$safeprojectname$.Data">
 Data.Template\MyTemplate.vstemplate
 </ProjectTemplateLink>
 </ProjectCollection>
 </TemplateContent>

</VSTemplate>
<pre>

If you use “Export Template” wizard from VS, export all projects from your solution, and unzip them into one folder.

image_thumb

In my example I have three projects, Data, Business and Web. Here is the folder structure.

image_thumb1

Now zip all the files and folders into one single package. Put it into your Visual Studio template folder.

{Documents}\Visual Studio 2010\Templates\ProjectTemplates

Or change your template location to your folder. Open VS -> Tools -> Options -> projects and solutions

image_thumb4

Now if you open VS and create a new project, you can find the template under C# projects.

—————————————————————————————–

Download the sample vstemplate from http://mvc3projecttemplate.codeplex.com/

Create a MVC 3 web solution template – Part 1, Create template projects

I am implementing a web application visual studio 2010 template at work. So it saves everyone’s time on a fresh start project. I decide to have 3 layers, presentation layer – web mvc3, business layer, and data access layer.

I have created a MVC3.WebTemplate solution with 3 projects, Business.Template , Data.Template and Web.Template. Let’s see what I have included in the three layers solution.

image

1. Web.Template

image

1.1 CodeTemplates

This folder contains the t4 templates to generate codes for views and controllers. I have included this in the project and it overrides the MVC default one. For more information, please read this blog https://kevww.wordpress.com/?s=t4+template

Have done this provides me ability to create my own t4 templates in the future, eg. in this template, I have modified the default template to make all controllers inherit a BaseController. When you create a new controller using the scaffolding template, it will create YourController : BaseController.

In the future I will create some view templates to make use of Twitter.Bootstrap and save you some time on styling the views.

1.2 Nuget Packages

I have included the latest packages for Jquery, Modernizr, Elmah, Ninject, Twitter.Bootstrap, etc.

<packages>
  <package id="elmah" version="1.2.0.1" />
  <package id="elmah.corelibrary" version="1.2.1" />
  <package id="Elmah.MVC" version="1.3.2" />
  <package id="EntityFramework" version="4.3.0" />
  <package id="jQuery" version="1.7.1" />
  <package id="jQuery.UI.Combined" version="1.8.17" />
  <package id="jQuery.Validation" version="1.9" />
  <package id="jQuery.vsdoc" version="1.6" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
  <package id="Modernizr" version="1.7" />
  <package id="Ninject" version="2.2.1.4" />
  <package id="Ninject.MVC3" version="2.2.2.0" />
  <package id="Twitter.Bootstrap" version="2.0.0" />
  <package id="WebActivator" version="1.4.4" />
</packages>

Of course, you know Nuget will download all the packages in a Package folder in your solution folder. You don’t need to include those files into your template. You can easily restore the Nuget packages to a new solution thsi is created from the template.

image

2. Business.Template

image

Not much to talk about for this project, really it should contain all your business logics.

3. Business.Template

image

3.1 DataContext

For data access, I am using EntityFramework 4.3. I have included a empty context edmx file with t4 templates to generate DbContext & POCOs.

If you want do CodeFirst, you can remove my stuff under Data folder and put your POCOs there.

3.2 Repositories

I have included a generic base repository. To help you start building your repositories. It has the default CRUD operation methods which you can inherit from.

————————————————————————————-

This is all about my project template, please refer to blog part 2 for the VS Template Creation.

Download the sample vstemplate from http://mvc3projecttemplate.codeplex.com/