Sunday, April 1, 2018

Sitecore 9 Forms - Server validation - “Email already exists”





Don’t want to save the duplicate data in the form? This is very basic requirement for each type of forms,

Email id should be unique, let’s think the subscription form, where email id is the primary key, we need to validate the unique email id before save the form data. – “email already exists”

In this blog I will explain, how we can place the server-side validation (email already exists) in the new Sitecore 9 forms module.




Let’s start with Sitecore 9 forms, open the form in the Sitecore:

Create the form as per your requirement and drag email field in the form:



Client validation for Email Address

Select the email field > go to the validation section on the right section
Checked the email validator checkbox for checking the valid email id.
We can also change the regular expression of the default Sitecore email validator as below:

Go to /sitecore/system/Settings/Forms/Validations/Email Validator
In parameter field, change the value of regular expression as below:


Add email validator in the forms email field:

Go to email field:
/sitecore/system/Settings/Forms/Field Types/Basic/Email

You can see email validator is selected under allowed validation field as below;






All done for client-side validation, all the above settings are by default provided by Sitecore.

Now, let’s explore the server-side validation,

Server-Side Validation – Email already exists:

Below is the high-level algorithm for server-side validation:



  1. Create the custom validation for forms in Sitecore
  2. Override the validation class and check if email already exists in the database through the code
  3. Assign validation to the email field



Create the custom validation for forms in Sitecore

Go to /sitecore/system/Settings/Forms/Validations
Create the Sitecore item based /sitecore/templates/System/Forms/Validation template

Fill the settings section fields such as type, message, parameters, provide custom class file name with namespace, provide custom error message in message field and parameter if any in parameter field.



Below is the code

using Sitecore.DependencyInjection;
using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
using Sitecore.ExperienceForms.Mvc.Models.Validation;
using Sitecore.ExperienceForms.Mvc.Models.Validation.Parameters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Linq;
using Sitecore.Analytics;
using Sitecore.Diagnostics;
using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Processing;
using Sitecore.ExperienceForms.Processing.Actions;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.Configuration;
using Sitecore.XConnect.Collection.Model;

using Microsoft.Extensions.DependencyInjection;
using Sitecore.ExperienceForms.Data.Entities;

namespace Demo.Feature.Form.Validation
{
    public class EmailExistValidation : ValidationElement<RegularExpressionParameters>
    {

        private IFormDataProvider _dataProvider;


        protected virtual IFormDataProvider FormDataProvider
        {
            get
            {
                IFormDataProvider formDataProvider = this._dataProvider;
                if (formDataProvider == null)
                {
                    IFormDataProvider service = ServiceLocator.ServiceProvider.GetService<IFormDataProvider>();
                    IFormDataProvider formDataProvider1 = service;
                    this._dataProvider = service;
                    formDataProvider = formDataProvider1;
                }
                return formDataProvider;
            }
        }

        public override IEnumerable<ModelClientValidationRule> ClientValidationRules
        {
            get
            {
                if (string.IsNullOrEmpty(this.RegularExpression))
                {
                    yield break;
                }
            }
        }

        protected virtual string RegularExpression
        {
            get;
            set;
        }

        protected virtual string Title
        {
            get;
            set;
        }

        public EmailExistValidation(ValidationDataModel validationItem) : base(validationItem)
        {

        }

        public override void Initialize(object validationModel)
        {
            object regularExpression;
            base.Initialize(validationModel);
            StringInputViewModel stringInputViewModel = validationModel as StringInputViewModel;
            if (stringInputViewModel != null)
            {
                this.Title = stringInputViewModel.Title;
            }
            RegularExpressionParameters parameters = base.Parameters;
            if (parameters != null)
            {
                regularExpression = parameters.RegularExpression;
            }
            else
            {
                regularExpression = null;
            }
            if (regularExpression == null)
            {
                regularExpression = string.Empty;
            }
            this.RegularExpression = (string)regularExpression;
        }

        public override ValidationResult Validate(object value)
        {
            if (value == null || string.IsNullOrEmpty(this.RegularExpression))
            {
                return ValidationResult.Success;
            }
            var formId = Guid.Parse(this.RegularExpression);
            var data = this.FormDataProvider.GetEntries(formId, null, null);
            foreach (var item in data)
            {
                var emailValue = item.Fields.Where(x => x.FieldName == "Emailaddress").FirstOrDefault();
                if (emailValue != null && emailValue.Value.ToLower() == value.ToString().ToLower())
                {
                    return new ValidationResult(this.FormatMessage(new object[] { this.Title }));
                }

            }
            return ValidationResult.Success;
        }
    }
}


Where Sitecore 9 Form data saved?
No more WFFM, all sitecore9 forms data goes to experienceforms database in below 2 tables:


  1. Fielddata
  2. FormEntry




Now, code part is done, its configure this validation on the form, I have created one form called subscriber form for email id as below:





This form contains only one field for email id, and we are checking if email already exists using Sitecore9 forms.

So, when you design your form in the form section in the Sitecore, it will create the items in the content tree under the form folder, next to content editor node, as below:







Here, we need to select the newly created validation called, “email exist validator” in the validations section.

Now all done, let’s check the validation:




I hope this article will help you to configure the custom error message in Sitecore 9 forms.

Happy Sitecoring.


Please connect me if you need any further information on this implementation on Ashish Bansal


Sunday, February 11, 2018

What a Good Start of the year 2018 with Sitecore 9







Excited to share this great information,  Go live with Sitecore 9 update1 on Azure PaaS.

We dared to opt the decision to go with Sitecore9 which is very new release and trust me it was not the easy decision to convey to the stake holders of the business to implement Sitecore 9 because the version is fairly very new  it was not the straight forward implementation, lots of new feature, which we had to explored, and researched including Bug Fixes, Support from Sitecore, patches etc. 

Also, Not too much documentation available in the market yet as the product is very new, but we took this implementation as an opportunity to implement with all innovative technology stack with Sitecore, as below:

  • Sitecore9
  • Azure PaaS
  • Sitecore 9 Forms
  • SXA 1.6
  • xConnect
  • Unicorn
  • MVC 5
  • Glass Mapper
  • bootstrap 4
  • CSS 3
  • HTML 5


I can say this was one of my challenging and interesting project with Sitecore 9 with lots of learning in the Sitecore9 and Azure PaaS configuration as below:

  • Sitecore 9 Forms Customization - Custom Server Validation, code injection
  • Sitecore xConnect - Customization 
  • Sitecore SXA 1.6 - Great learning as this is the first project on SXA
  • Azure DDoS Protection
  • Azure CDN Configuration - Standard Verizon
  • Azure Custom Binding and SSL Configuration
  • Azure Dynatrace configuration



Again, thanks to the development team and Sitecore product team specially Nilesh Thakkar who made this journey possible for their extended support in this project.

Please have a look of this website:



Sitecore 9 Forms:



Please connect me if you need any further information on this implementation on Ashish Bansal

Happy SItecoring 9


Monday, December 11, 2017

Schedule publishing with #Sitecore Workflow





“Workflows provide a flexible and controllable way of content creation, maintenance, and review. Sitecore's workflow facilities make it possible to quickly define sophisticated workflows, often without writing any code at all.”
Workflow is well known feature in the Sitecore, you can extend/customize it accordingly to the business need, states of the workflow can be configurable and extend as per the requirement.

Every workflow has the last state called final state, where Item is ready for publish, Sitecore also have the default publish state called Approved, with Auto Publish Action, which means once the item is in Approved State, it can be Auto Publish.

If you’re not familiar with the Sitecore workflow, you can refer the below posts:





Problem statement

We had the requirement “capability to scheduled publishing during the last state of the workflow”, that’s called “Publisher”.

Publisher can able to publish the content immediately or can scheduled for publishing for the future date, for example publisher want to launch the campaign in the mid night, so they can do the schedule publishing with the future date.


Solution:

Below Is the high-level solution to achieve the above requirement:

  1. Provide 2 features to the publisher one is Publish Now and other is Schedule Publish, So the publisher can decide what to do?
  2. If publisher click on Schedule Publish, it will prompt the dialog  to  get the input as “DateTime”
  3. Schedule the item for publish with provided “Datetime”
  4. Run the agent in the background, which will execute in the specific interval (30minutes) and check if scheduled Item is Ready for publishing or not, if yes it will publish the Item.


Below is the step by step implementation:

  • Create the state called Publish in the workflow
  • Create 2 command called Publish now and Schedule Publish in the Publish state as below:

  • Publish Now command simple called the next state called published, which have the default action Auto-publish, which means if the publisher click on Publish now, content will be published directly

  • “Schedule publish” command has the field called “comment Template”, Thanks @Sitecore to provide the Comment Template field, where we can configure the different type of field along with comment field

  • I created the custom Comment template called schedule which has 2 fields comments and “Schedule DateTime “

  • Now, the “schedule publish” command will prompt the comment template fields which we just configured here, in the below image, we can see the 2 command which we have configured in the publish workflow state “Publish Now” and “schedule Publish”



  • This dialog box prompt the schedule datetime, which we can use as the parameter later, so now publisher can have 2 option, publish now and schedule publish and on the click on schedule publish, the above dialog will prompt for schedule datetime
  • Now, if we consider the workflow state called “Publish” and look into the “schedule publish” command, it will call the action named “Schedule” under the next stage called “scheduled”, which mean if the publisher click on the schedule publish button, it prompt the dialog box(comment Template) and call the “Schedule” action.

  • I have configured the custom class function(Sitecore.Foundation.Publishing.ScheduleAction, Sitecore.Foundation.Publishing) in the field called “Type string” in  Schedule” action Item, as highlighted in the above image.
Below Is the code of the “ScheduleAction” function, which create the queue in the sitecore using the sitecore Item in the specific folder.


using Sitecore.Data.Items;
using Sitecore.Foundation.SiteSetting.Repositories;
using Sitecore.Workflows.Simple;
using System;
using System.Collections.Generic;
using Sitecore.Tasks;
using System.Linq;
using System.Web;
using Sitecore.Data.Fields;

namespace Sitecore.Foundation.Publishing
{
    public class ScheduleAction
    {
        public void Process(WorkflowPipelineArgs args)
        {
            PushItemToQueue(args);
        }

        public void PushItemToQueue(WorkflowPipelineArgs args)
        {
            if (args.DataItem == null)
                return;

            //Read the comments template field
            if (args.CommentFields["Schedule DateTime"] != null && string.IsNullOrEmpty(args.CommentFields["Schedule DateTime"]) == false)
            {
                var sourceItem = args.DataItem;
                string scheduleTime = args.CommentFields["Schedule DateTime"];

                //create the item Name
                string ItemName = ItemUtil.ProposeValidItemName(string.Format("{0}SchedulePublishQueue", sourceItem.ID.ToString()));

                using (new Sitecore.SecurityModel.SecurityDisabler())
                {
                    // Get the master database
                   Sitecore.Data.Database master = Sitecore.Data.Database.GetDatabase("master");
                    // Get the template for which you need to create item
                    Sitecore.Data.Items.TemplateItem template = master.GetItem(new Data.ID(Constants.schedulePublishQueueTemplateId)); //  ("/sitecore/templates/Sample/Sample Item");
                    // Get parent Item
                    Item parentItem = master.GetItem(new Data.ID(Constants.schedulePublishQueueParentFolderId));
                    // Add the item to the site tree
                    Item publishQueueItem = parentItem.Add(ItemName, template);
                    publishQueueItem.Editing.BeginEdit();
                    try
                    {
                        // Assign values to the fields of the new item
                        publishQueueItem.Fields["Publish Item Id"].Value = sourceItem.ID.ToString();
                        publishQueueItem.Fields["Publish Item Path"].Value = sourceItem.Paths.Path;
                        publishQueueItem.Fields["Publish Schedule Date"].Value = scheduleTime;
                        publishQueueItem.Editing.EndEdit();
                    }
                    catch (System.Exception ex)
                    {
                        // Log the message on any failure to sitecore log
                        Sitecore.Diagnostics.Log.Error("Could not update item " + publishQueueItem.Paths.FullPath + ": " + ex.Message, this);
                        publishQueueItem.Editing.CancelEdit();
                    }
                }


              
            }
        }
    }
}




I have created the template called “SchedulePublishQueue” which will be used at the time of  Sitecore item creation when someone click on “schedule publish” button(for temporary queuing purpose)




Below is the output of the above function:


The above function creates the Sitecore items using the template “SchedulePublishQueue” and read the fields from the comment parameter template and set the value in the SchedulePublishQueue template field for example the “Publish schedule Date” Field.

Now, in this way, we can create the publishing schedule queue within the sitecore itself, which is easy to maintain.


Note: we can also create this queue in the external database as well.




Now everything done, Items has been scheduled, what Next? 
I Need some Job/ Agent/ service which can run in the background, and can configure in the Sitecore.

The moment I thought the sitecore background Job, I remembered the Akshay sura module named “SiteCron”, Yes “SiteCron is the Sitecore module for your everyday scheduling needs” you can find the more details from here,



But I just used the sitecore default agent, As I was in the hurry to implement this POC, so below is the agent that I have configured which will execute in the background with the interval of 1 hour.

<agent type="Sitecore.Foundation.Publishing.Tasks.PublishScheduleCommand, Sitecore.Foundation.Publishing"             method="Run" name ="Auto Publish Schedule" interval="00:01:00">      </agent>

Basically, this agent will check the schedule publish queue which we just created above, and validate the “Publish schedule Date” time with the current time, If time is less than and equals to the current datetime, it will publish the item and then recycle it, so it will be removed from the scheduled publish queue, so evertime, queue would be clean, below is the code of the agent :



using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.SecurityModel;
using Sitecore.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Sitecore.Foundation.Publishing.Tasks
{
    public class PublishScheduleCommand
    {
        Database webdb = Sitecore.Configuration.Factory.GetDatabase("web");
        Database masterdb = Sitecore.Configuration.Factory.GetDatabase("master");

        public void Run()
        {
            Item rootFolder = masterdb.GetItem(new Data.ID(Constants.schedulePublishQueueParentFolderId));
            IEnumerable<Item> unPublishedItem = rootFolder.Axes.GetDescendants().Where
                ( (x => !string.IsNullOrEmpty(x["Publish Item Id"]) && !string.IsNullOrEmpty(x["Publish Schedule Date"]) &&  GetDateTimeFieldValue(x.ID.ToString(), "Publish Schedule Date") <= DateTime.UtcNow)
                );

            if (unPublishedItem != null && unPublishedItem.Count() > 0)
            {
                foreach (var ScheduleQueueItem in unPublishedItem)
                {
                    //Get Schedule publish Item
                    Item scheduleItem = masterdb.GetItem(new Data.ID(ScheduleQueueItem["Publish Item Id"]));
                    PublishScheduleItem(scheduleItem, ScheduleQueueItem);
                }
            }
           
        }
        /// <summary>
        /// by: Ashish Bansal
        /// For: Publish the schedule sitecore Item, Recyle the schedule queue and change the workflow state
        /// </summary>
        /// <param name="actualPublishItem"></param>
        /// <param name="ScheduleQueueItem"></param>
        private void PublishScheduleItem(Item actualPublishItem, Item ScheduleQueueItem)
        {

            try
            {
                using (new Sitecore.SecurityModel.SecurityDisabler())
                {
                    // target databases
                    Database[] databases = new Database[1] { webdb };
                    Sitecore.Handle publishHandle = Sitecore.Publishing.PublishManager.PublishItem(actualPublishItem, databases, webdb.Languages, false, false);
                   
                    // change the workflow state
                    actualPublishItem.Editing.BeginEdit();
                    actualPublishItem["__Workflow state"] = Constants.publishedWorkflowStateID;
                    actualPublishItem.Editing.EndEdit();

                    ScheduleQueueItem.Editing.BeginEdit();
                    ScheduleQueueItem["Published DateTime"] = DateUtil.ToIsoDate(DateTime.Now);
                    ScheduleQueueItem["Is Published"] = "true";
                    ScheduleQueueItem.Editing.EndEdit();

                    //remove from the Publishing Queue
                    ScheduleQueueItem.Recycle();


                }
            }
            catch (Exception ex)
            {
                Sitecore.Diagnostics.Log.Error("Sitecore.Foundation.Publishing.Tasks.PublishScheduleCommand.PublishScheduleItem" + " Error in" +   ex.Message, this);
            }
          
          
        }
        private DateTime GetDateTimeFieldValue(string currentItemID, string fieldName)
        {
            Sitecore.Data.Database master = Sitecore.Data.Database.GetDatabase("master");
            Item currentItem = master.GetItem(new Data.ID(currentItemID));
            DateField fieldValue = currentItem.Fields[fieldName];
            DateTime publishDate = DateTime.MinValue;
            if ((fieldValue != null) && (fieldValue.InnerField.Value != null))
                publishDate = DateUtil.IsoDateToDateTime(currentItem.Fields[fieldName].Value);
            return publishDate;
        }
    }
}

Note: There is a scope of optimization in the above solution, this is just for POC purpose, like I said, we can use “SiteCron” for the background Job, we can maintain the queue in the external system, we can provide more features, Code optimization( using Index search API to get the schedule queue Items) etc, etc..

I hope this article will help you for schedule publishing in the workflow:


 Happy Sitecoring