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.


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

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,


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);
        }

    }
}


Use Validation Attribute on your Model Class.

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; }
}
}

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?

[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);

        }

MVC View for MyForm


@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>


}

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

Advertisements

5 Responses to “Sitecore MVC form fields validations”

  1. Nate Hase Says:

    It is important to keep in mind that the Item ID that these attributes use to find their values must be a constant value, known at compile-time. Attributes don’t know anything about the model/class whose property they decorate.

    In order to make this solution work (and it’s a pretty cool one) we need to architect our form validation settings in Sitecore in such a way as to make the ID of those settings items available as constants in our code. This means that we cannot allow the content authors to create new validation settings items since our code needs to know the IDs (technically new settings can be created, but they will not be used without a code update).

    This implies a separation of form settings like Field Labels and other messages from form validation settings like a Required Field Error message or a regex.

    • Nate Hase Says:

      Just realized that the Sitecore.Context is available within the context of an Attribute. In this case the constants in the attribute code need only be written to facilitate the finding of the data item you require. For example, you could find the form rendering in presentation and get the appropriate validation settings from its data source. That’s so much better! 🙂

  2. Nisheesh Kulshreshtha Says:

    I am new to Sitecore MVC, and this worked like a charm…
    Thanks Brijesh..

  3. nisheesh Says:

    I am new to Sitecore MVC, and this worked like a charm…
    Thanks Brijesh…


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: