Sitecore MVC form fields validations

Are you working on Sitecore MVC and want to submit data from a form using controller rendering? But before you submit your data you want to undergo a validation process where you want to perform form fields validation. Also you want to use the power of Sitecore to draw the validation messages from an item instead of placing them as attribute value. Well, yes using System.ComponentModel.DataAnnotations namespace you can gain advantage and use RequiredAttribute, RegularExpressionAttribute and other classes to pass on the ErrorMessage by specifying it on your model class.

[code language="c-sharp"]

[Required(ErrorMessage="First Name is required")][SitecoreField(FieldName = "FirstName")]public virtual string FirstName { get; set; }

[/code]

This isn’t a flexible approach and you are placing errormessages more of a hardcoded way.

How about drawing these messages from a Sitecore Item? Let’s see how!

Create a SitecoreValidationAttribute class

Like other Validaiton Attribute classes make this class inherit from ValidationAttribute class. It might look something similar as below,

[code language="c-sharp"]

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.ComponentModel.DataAnnotations;using Sitecore.Data.Items;using Newtonsoft.Json;using Sitecore.Data;

namespace SitecoreExtensions.Attributes{ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)] public class SitecoreValidationAttribute : ValidationAttribute { public ValidationAttribute InnerValidationAttribute { get; set; }

private Item _item; private string _id; private string _field; private string _jsonProperties; private Type _type;

public SitecoreValidationAttribute() { } /// <param name="type">Type of ValidationAttribute to use.</param> /// <param name="id">Item ID of the Sitecore item to use for getting the error message string.</param> /// <param name="field">Field name of the Sitecore item to use for getting the error message string.</param> /// <param name="jsonProperties">Optional Json representation of properties to set on the validation attribute.</param> /// <param name="pattern">RegEx to be used for RegularExpressio Validations</param> public SitecoreValidationAttribute(Type type, string id, string field, string jsonProperties = null, string pattern = null) : base() { if (type == typeof(RegularExpressionAttribute)) { InnerValidationAttribute = new RegularExpressionAttribute(pattern);//Activator.CreateInstance(type) as RegularExpressionAttribute; } else { InnerValidationAttribute = Activator.CreateInstance(type) as ValidationAttribute;

} _type = type; _id = id; _field = field; _jsonProperties = jsonProperties;

SetErrorMessage(); if (!string.IsNullOrWhiteSpace(_jsonProperties)) SetProperties(); }

private void SetProperties() { dynamic jsonProperties = JsonConvert.DeserializeObject(_jsonProperties); foreach (var property in jsonProperties) { var propertyName = property.Name; var propertyValue = property.Value.Value;

// Get a property on the type that is stored in the // property string var propertyInfo = _type.GetProperty(propertyName);

// Set the value of the given property on the given instance propertyInfo.SetValue(InnerValidationAttribute, propertyValue, null); }

}

private void SetErrorMessage() { if (ID.IsID(_id)) { var db = Sitecore.Context.Database; if (db == null) return;

_item = db.GetItem(new ID(_id));

if (_item != null) { var fieldValue = _item[_field]; if (!string.IsNullOrWhiteSpace(fieldValue)) { ErrorMessage = fieldValue; } } } }

public override bool IsValid(object value) { return InnerValidationAttribute.IsValid(value); }

// We are overriding TypeId, because during validation, the validator only allows 1 instance of an attribute // to be used on a property at a time. This validation attribute is written such that it is a "generic" attribute // that can be used many times to act like the inner validation attribute. We override the typeid and set it // to the value of the inner validation attribute type. public override object TypeId { get { return InnerValidationAttribute.TypeId; } }

public override string FormatErrorMessage(string name) { SetErrorMessage(); InnerValidationAttribute.ErrorMessage = ErrorMessage; return InnerValidationAttribute.FormatErrorMessage(name); }

protected override ValidationResult IsValid(object value, ValidationContext validationContext) { return base.IsValid(value, validationContext); }

}}

[/code]

Use Validation Attribute on your Model Class.

[code language="c-sharp"]namespace Sitecore.Models.Components{Public class MyFormData{private const string FormId = "{FB76CF1E-E172-4069-8989-8087C75836EF}";[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "First Name Required Message")] [SitecoreField(FieldName = "FirstName")] public virtual string FirstName { get; set; }[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "Last Name Required Message")] [SitecoreField(FieldName = "LastName")] public virtual string LastName { get; set; }[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "Address Required Message")] [SitecoreField(FieldName = "Address")] public virtual string Address { get; set; }[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "State Required Message")] [SitecoreField(FieldName = "State")] public virtual string State { get; set; }[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "Zip Code Required Message")] [SitecoreValidation(typeof(RegularExpressionAttribute), privacyFormId, "Zip Code Format Message","", @"(^\d{5}$)")] [SitecoreField(FieldName = "Zip")] public virtual string ZipCode { get; set; }

[SitecoreValidation(typeof(RequiredAttribute), privacyFormId, "Phone Required Message")] [SitecoreValidation(typeof(RegularExpressionAttribute), privacyFormId, "Phone Format Message", "", @"(^\d{10}$)")] [SitecoreField(FieldName = "Phone")] public virtual string Phone { get; set; }}}

[/code]

All the magic is done by decorating model class property with SitecoreValidationAttribute, Parameters that are passed to SitecoreValidationAttribute are:

  • ItemId: ID of the item from which the validation messages would be drawn.
  • Field ID: Name of the field from which the validation message would be drawn.
  • Pattern (Optional): In case of RegularExpression validation you might want to pass the RegEx (pattern) that would be used to validate the form field value. In this blog post I have exemplified zip code and phone number to validate 5 and 10 digit numbers.

How does a controller look like?

[code language="c-sharp"][HttpGet]public ActionResult ProcessForm() { MyFormData model = defaultModel(); if (model == null) { return new EmptyResult(); } return View("~/Views/Renderings/Sections/Components/MyForm.cshtml", model); }[HttpPost] public ActionResult ProcessForm (MyFormData model) { try {

if (ModelState.IsValid) {

//Perform required processing here may be to save data into a sitecore item, //a database or a text file //Pass success message ModelState.AddModelError("", model.SuccessAlert);} else {

ModelState.AddModelError("", model.ErrorAlert); } } catch (Exception ex) {Log.Error(ex.StackTrace,this); }

var combinedmodel = this.CombineModel(model);

return View("~/Views/Renderings/Sections/Components/MyForm.cshtml", model);

}

[/code]

MVC View for MyForm

[code language="c-sharp"]

@inherits Glass.Mapper.Sc.Web.Mvc.GlassView<Sitecore.Models.Components.MyFormData>

@using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post)){ @Html.Sitecore().FormHandler()

<div class="page-content form-group">

<div class="row">

<div class="form-group col-sm-12"> <label class="sr-only" for="FirstName">@Model.FirstName</label> @if (string.IsNullOrEmpty(Model.FirstName)) { @Html.TextBoxFor(x => x.FirstName, new { @class = "form-control Login-form-input", @type = "text", @id = "FirstName", @placeholder = @Model.FirstNamePlaceholder, @Value = "", @maxlength = "20" }) } else { @Html.TextBoxFor(x => x.FirstName, new { @class = "form-control Login-form-input", @type = "text", @id = "FirstName", @Value = @Model.FirstName, @maxlength = "20" }) }

<div class="alert alert-danger text-left"> @Html.ValidationMessageFor(x => x.FirstName) </div>

</div>

</div>

<div class="row">

<div class="form-group col-sm-12"> <label class="sr-only" for="LastName">@Model.LastName</label> @if (string.IsNullOrEmpty(Model.LastName)) { @Html.TextBoxFor(x => x.LastName, new { @class = "form-control Login-form-input", @type = "text", @id = "LastName", @placeholder = @Model.LastNamePlaceholder, @Value = "", @maxlength = "20" }) } else { @Html.TextBoxFor(x => x.LastName, new { @class = "form-control Login-form-input", @type = "text", @id = "LastName", @Value = @Model.LastName, @maxlength = "20" }) }

<div class="alert alert-danger text-left"> @Html.ValidationMessageFor(x => x.LastName) </div>

</div>

</div>

<div class="row">

<div class="form-group col-sm-12"> <label class="sr-only" for="Address">@Model.Address</label> @if (string.IsNullOrEmpty(Model.Address)) { @Html.TextBoxFor(x => x.Address, new { @class = "form-control Login-form-input", @type = "text", @id = "Address", @placeholder = @Model.AddressPlaceholder, @Value = "", @maxlength = "40" }) } else { @Html.TextBoxFor(x => x.Address, new { @class = "form-control Login-form-input", @type = "text", @id = "Address", @Value = @Model.Address, @maxlength = "40" }) }

<div class="alert alert-danger text-left"> @Html.ValidationMessageFor(x => x.Address) </div>

</div>

</div>

@if (Model.States != null && Model.States.Count() > 0) {

<div class="row">

<div class="form-group col-sm-12 col-md-7"> <label class="sr-only" for="State">@Model.State</label> @Html.DropDownListFor(x => x.State, new SelectList(Model.States, "Key", "Value"), Model.StatePlaceholder, new { @class = "form-control Login-form-select" })

<div class="alert alert-danger text-left"> @Html.ValidationMessageFor(x => x.State) </div>

</div>

</div>

}

<div class="row">

<div class="form-group col-sm-12 col-md-7"> <label class="sr-only" for="ZipCode">@Model.ZipCode</label> @if (string.IsNullOrEmpty(Model.ZipCode)) { @Html.TextBoxFor(x => x.ZipCode, new { @class = "form-control Login-form-input", @type = "text", @id = "ZipCode", @placeholder = @Model.ZipCodePlaceholder, @Value = "", @maxlength = "5" }) } else { @Html.TextBoxFor(x => x.ZipCode, new { @class = "form-control Login-form-input", @type = "text", @id = "ZipCode", @Value = @Model.ZipCode, @maxlength = "5" }) }

<div class="alert alert-danger text-left"> @Html.ValidationMessageFor(x => x.ZipCode) </div>

</div>

</div>

<div class="form-group submit-container"> <input type="submit" name="submitButton" value="Submit" aria-label="Submit form" role="button" id="submitButton" class="btn btn-primary"> <a class="btn btn-secondary" href="#" name="privacyClearButton" id="privacyClearButton">Clear</a> </div>

@Html.TextBoxFor(x => x.SuccessAlert, new { @type = "hidden", @id = "SuccessAlert", @Value = @Model.SuccessAlert }) @Html.TextBoxFor(x => x.ErrorAlert, new { @type = "hidden", @id = "ErrorAlert", @Value = @Model.ErrorAlert })

</div>

}

[/code]

That is all I have to share for now, keep reading and sharing Sitecore.