• Skip to primary navigation
  • Skip to main content

Josh Withee

  • Home
  • Blog
  • Contact
  • Show Search
Hide Search

ASP.NET Core 3.x Input Validation

Josh Withee · March 25, 2020 · Leave a Comment

See a mistake here? Or something missing? Let me know!

Use C# Attributes from the System.ComponentModel.DataAnnotations namespace to add server-side and client-side validation.

Tip: Keep your view model separate from your database model, because the same data attributes will do two different things: Set validation rules, and set characteristics of the database columns when you use code-first database generation. If you want to use only a single model, you could avoid the data-attributes altogether and only use custom validation attributes to create validation rules.

using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace ValidationAttributesProject.Entities
{
    public class Customer
    {
        public long CustomerId { get; set; }
        [StringLength(50)]
        public string Name { get; set; }
        [EmailAddress]
        public string Email { get; set; }
        [Phone]
        public string Phone { get; set; }

        public int YearToDateOrderCount { get; set; }
        [RegularExpression(@"\d\d?/\d\d?/\d\d\d\d")]
        [MinDate(2000,1,1)]
        public string AccountCreationDate { get; set; }
        [RegularExpression(@"\d\d?/\d\d?/\d\d\d\d")]
        // could do another custom validation attribute to make sure this date comes after AccountCreationDate
        public string FirstTransactionDate { get; set; }

    }
}

Notice the [MinDate(2000,1,1)] attribute – that’s a custom one that validates that the string input for this property indicates a date that is not before January 1, 2000. Here is how to define that custom attribute:

using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace ValidationAttributesProject.Entities
{
    public class MinDateAttribute : ValidationAttribute
    {
        public MinDateAttribute(int year, int month, int day)
        {
            Year = year;
            Month = month;
            Day = day;
        }

        public int Year { get; set; }
        public int Month { get; set; }
        public int Day { get; set; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // Make sure that the input is not earlier than the date on the validation attribute
            string dateInput = (string)value;
            (int year, int month, int day) = getYearMonthDayIntegers(dateInput);
            if ((year * 10000 + month * 100 + day) < (Year * 10000 + Month * 100 + Day))
                return new ValidationResult($"Date must be no earlier than {Month}/{Day}/{Year}.");
            else
                return ValidationResult.Success;
        }

        Regex dateRegex = new Regex(@"(?<Month>\d{1,2})/(?<Day>\d{1,2})/(?<Year>\d{4})");
        private (int, int, int) getYearMonthDayIntegers(string date)
        {
            Match m = dateRegex.Match(date);
            return (int.Parse(m.Groups["Year"].Value),
                    int.Parse(m.Groups["Month"].Value),
                    int.Parse(m.Groups["Day"].Value));
        }
    }
}

Shown below is the View. Notice these things:

  • <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  • <span asp-validation-for="CustomerId" class="text-danger"></span>
  • @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
@model ValidationAttributesProject.Entities.Customer

@{
    ViewData["Title"] = "View";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>View</h1>

<h4>Customer</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="View">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="CustomerId" class="control-label"></label>
                <input asp-for="CustomerId" class="form-control" />
                <span asp-validation-for="CustomerId" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Email" class="control-label"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Phone" class="control-label"></label>
                <input asp-for="Phone" class="form-control" />
                <span asp-validation-for="Phone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="YearToDateOrderCount" class="control-label"></label>
                <input asp-for="YearToDateOrderCount" class="form-control" />
                <span asp-validation-for="YearToDateOrderCount" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="AccountCreationDate" class="control-label"></label>
                <input asp-for="AccountCreationDate" class="form-control" />
                <span asp-validation-for="AccountCreationDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FirstTransactionDate" class="control-label"></label>
                <input asp-for="FirstTransactionDate" class="form-control" />
                <span asp-validation-for="FirstTransactionDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

And here are the contents of _ValidationScriptsPartial.cshtml:

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

Finally, note the use of ModelState.IsValid in the Controller action:

// POST: Customer/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Customer model)
{
    if (!ModelState.IsValid)
    {
        // Validation rules did not pass
        return View(model);
    }
    else
    {
        // Code here for valid input scenario
        return RedirectToAction(nameof(Index));
    }
}

Other helps:

  • What if I need to use my DbContext for validation?

Related

Uncategorized ASP.NET Core, DataAnnotations, Input Validation

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Copyright © 2025