Monday, January 11, 2016

Item Bucket And Key Design Challenges.

The Item bucket has been introduced in 7.0 and later version of sitecore. From its inception either very few project must have implemented the concepts and must have realized the true benefits.

Real time case study:-
  • Country /State/City Item bucket.. Locate stores , service centres
  • Articles/blogs etc.
Key Design and Run time Challenges:
  • Sync the Bucket when data is dynamic. It takes time and can impact the performance.
  • Index and Bucket query for large data set is still the problem. Depends what is part of the index provider is it -Lucene or Solr.
  • Sync Strategy of item bucketed collections. Bucket(Sync)
  • The baseline for Bucketing with Index is difficult to achieve as stats varies based on infrastructure.
  • Link providers to understand URL to support SEO.
Create Item Bucket.-For given example. CarTypeFolder and Car template. 
  1. Select standard value item of Car Template and click check box bucketable.
  2. Apply Rules under Settings- Buckets -Item Bucket Settings. For my example I chose rule Specific template- in this case its CarTypeFolder whereas its contain item Car Template is marked bucketable in standard field. Bucket applied on folder with based on ID.
  3. Go to Content tree..select Folder that is part of CarTypefolder and go to configure and click on Bucket and Sync. For reference see the screenshot.
1. Set Car Standard Values =Bucketable

2. Set Rules as per CarTypeFolder template and based on ID -Folder structure
3. Go to content tree, Select Super Car which is part of CarTypeFolder. Go to ribbon Configure and select Bucket and sync.

Still to write about- Indexing with Item bucket Context Query and Implementation.





Custom Workflows in Sitecore

Purpose
Business workflow
  • Content Author- Submit
  • Content Reviewer-Approve/Reject 
  • Final State -Approved.

Technical Workflow 
  • State
  • Command 
  • Action and Validation Action.

Workflow Snapshot


How to create it?
  1. Open Sitecore in desktop mode and go to content Editor Screen. Go to Workflows and create Workflow with State, Command and Action Template.
  2. As per above workflow, create Draft State with Submit action. On Submit action select Data-> Next State Awaiting Approval.
  3. Create Awaiting Approval State with Two command Approve and Reject. Under Approve create three action - Auto publish(Type String : Sitecore.Workflows.Simple.PublishAction, Sitecore.Kernel, Param: Deep=1). SyncCustomDb Action(Reference .net bin dll) and Validation Action to report error , warnings to business users.[Data->Type->Sitecore.Workflows.Simple.ValidatorsAction, Sitecore.Kernel]
  4. What next, you tick mark.suppress comment if you want to do so.
  5. Create final Approved State within Awaiting Approval state. For Final Approved state ensure to mark Final checkbox .
  6. For Reject State select Next state to Draft state.
Template used
  • State
  • Action
  • Command
  • Validation Action
Important Library

  • Sitecore.Workflows.Simple.ValidatorsAction, Sitecore.Kernel
  • Sitecore.Workflows.Simple.PublishAction, Sitecore.Kernel

Publish Parameters
Publish action accepts 6 parameters:
  1. "deep" - controls whether children of the current item will be published. Possible values: "1" - children of the current item will be published; all other values - children of the current item will not be published.
  2. "related" - controls whether related items of the current item will be published. Possible values: "1" - related items of the current item will be published; all other values - related items of the current item will not be published.
  3. "targets" - comma (,) separated list of database names that item will be published to. Note, that this parameter does not expect a list of publishing target names, it expects list of database names.
  4. "alllanguages" - controls whether current item will be published in all languages that exist in source database. Possible values: "1" - current item will be published in all languages that exist in source database; all other values - code uses values of other parameters to determine languages in which current item will be published.
  5. "languages" - comma (,) separated list of languages in which current item will be published.
  6. "itemlanguage" - controls whether current item will be published in its current language. Possible values: "1" - current item will be published in its current language; "0" - current item will not be published in its current language; all other values - current item will published in its current language. Note that even if value of this parameter is "0", current item will still be published in its current language if current language of the item is in "languages" list.
Querystring parameter
deep=1&related=1&alllanguages=1

Auto Publish


Custom Workflow Action: SyncCustomDB
Suppose you want to sync your sitecore CMS data with custom database for Ecommerce database products or promotions if your enterprise solution doesn't provide that. The approach can vary with respect to problem and business requirement.

Here is the quick implementation.
Create Library with Workflow Pipeline processor and create new process .This process requires workflowPipelineArgs
Assign this workflow to the page item and then when business user approves the sitecore content for given item -template the action event will be performed to sync custom database. These can be anything, we can even replace this with any functionality such email notification or anything that needs to be done in case of approve content.


CustomWorkflowActionEvents

using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.Workflows.Simple;
using Sitecore.Diagnostics;
using Sitecore.Data.Items;
using Sitecore.Configuration;
using Sitecore.Data;

namespace Demo.Sitecore.Practical.CustomWorkflowActionEvents
{
    public class CustomDatabaseSync
    {
        public void Process(WorkflowPipelineArgs args)
        {
            using (var db = new SomeCustomDBContext())
            {
                Item pageItem = args.DataItem;

                if (pageItem.TemplateName == "Promotion")
                {
                    var tbPromotion = db.Promotions.Where(x => x.PromotionID == workFlowItem.ID.Guid);

                    if (tbPromotion.Any())
                    {
                        var tb = tbPromotion.FirstOrDefault();
                        tb.PromotionName = workFlowItem.GetFieldValue("PromotionName");
                        tb.StartDate = workFlowItem.GetDateTime("StartDate");
                        tb.EndDate = workFlowItem.GetDateTime("EndDate");
                        tb.IsEnabled = true;
                        tb.UpdatedDate = DateTime.Now;
                        db.Entry(tb).State = System.Data.EntityState.Modified;
                    }
                    else
                    {
                        db.Promotions.Add(new Promotion
                        {
                            PromotionID = workFlowItem.ID.Guid,
                            PromotionName = workFlowItem.GetFieldValue("PromotionName"),
                            StartDate = workFlowItem.GetDateTime("StartDate"),
                            EndDate = workFlowItem.GetDateTime("EndDate"),
                            IsEnabled = true,
                            CreatedDate = DateTime.Now,
                            UpdatedDate = DateTime.Now
                        });
                    }
                }               
                db.SaveChanges();
            }
        }
    }
}

Snapshot


References
http://www.sitecore.net/learn/blogs/technical-blogs/reinnovations/posts/2014/03/auto-publish-workflow-action-updates.aspx


Sitecore Querystring Param- Cheatsheat

Below are the list of querystring param that comes handy when we are in production deployment and wants to troubleshoot the deployment status in each and every sitecore delivery server. This is very useful querystring parameter at times where there are separate Content managed –Authoring server and several load balanced delivery server, these querystring helps in finding the various behaviour of page and act as troubleshooting tools. 

yes there are various ways to look for sitecore errors such as data folder logs, sitecore analyzer, IIS logs, event logs etc.


Sitecore Querystring param -Cheatsheat


Sr.No
SC param
Description
1.
?Sc_mode= {normal, Edit, Preview}
Change display mode of website
2.
?Sc_lang=
Change website language
Sitecore/system/languages
en-us
3
?Sc_site
Use to change website for multisite single instance.
4
?sc_itemid={123455678-14564-1234-1234-123456789ABC}
Change current item
5
?sc_device={mobile, default etc}
Adaptive design
6
?sc_database={master, web}
Change data in draft vs publish
7
?sc_debug={0,1}
0=false, 1=true
8
?sc_prof={0,1}
Switch on sitecore profiler
9
?sc_trace={0,1}
Switch trace on and off
10
?sc_ri={0,1}
Show hide rendering information

Sunday, January 10, 2016

Sitecore MVC with Dynamic Placeholder

Introduction
This is demo project with Car example.

Technical Use Cases
  • Dynamic Placeholder with configured pipeline.
  • View Rendering and Controller Rendering 
  • Static Placeholder
  • Repository to create Sitecore.Mvc.Presentation.RenderingContext Context.

Sitecore Design Layer

  • Layouts
  • Placeholders
  • Renderings
  • Items
  • Reference -Data collection

Sitecore MVC
  • View- Layouts
  • View- Container 
  • View-TextImage
  • View-Header
  • View-Footer
  • Controller- FooterController
  • Controller- TextImageController
  • Controller- HeaderController
  • Repository for above Controller


Three column Dynamic Placeholder

@Html.Sitecore().DynamicPlaceholder("sub-content-three-column-content-left")
@Html.Sitecore().DynamicPlaceholder("sub-content-three-column-content-middle")
@Html.Sitecore().DynamicPlaceholder("sub-content-three-column-content-right")
Extend Sitecore MVC using SitecoreHelper Sitecorehelper to extend the @Html.Sitecore.Placeholder("")
namespace Demo.Sitecore.CarInfo.WebSite.Utility
{
    public static class SitecoreExtensions
    {
        public static HtmlString DynamicPlaceholder(this SitecoreHelper helper, string key)
        {
            Guid currentRenderingId = RenderingContext.Current.Rendering.UniqueId;
            return helper.Placeholder(string.Format("{0}#{1}", key, currentRenderingId.ToString()));
        }
    }
}

Ultimately this dynamic placeholder fetch the textimage for respective layouts from 
TextImage Controller Rendering.



TextImageRender Repository

using Demo.Sitecore.CarInfo.WebSite.Models;
using Sitecore.Mvc.Presentation;
using Sitecore.Web.UI.WebControls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Demo.Sitecore.CarInfo.WebSite.Repository
{
    public class TextImageRepository
    {
        public TextImage GetTextImage()
        {
            var item = RenderingContext.Current.Rendering.Item;
            var textImage = new TextImage()
            {
                Title = new HtmlString(FieldRenderer.Render(item, "Title")),
                Image = new HtmlString(FieldRenderer.Render(item, "Image"))
            };
            return textImage;
        }
    }
}

TextImage Model


namespace Demo.Sitecore.CarInfo.WebSite.Models
{
    public class TextImage
    {
        public HtmlString Title         { get; set; }
        public HtmlString Description   { get; set; }
        public HtmlString Image         { get; set; }
    }
}
TextImage Controller
namespace Demo.Sitecore.CarInfo.WebSite.Controllers
{
    public class TextImageController : SitecoreController
    {
        public ActionResult TextImageDetail()
        {
            TextImageRepository repo = new TextImageRepository();
            return View(repo.GetTextImage());
        }
    }
}

Create Pipeline to Process DynamicPlaceholderRendering

namespace Demo.Sitecore.CarInfo.WebSite.Utility
{
    public class GetDynamicPlaceholderRenderings : GetAllowedRenderings
    {
        public new void Process(GetPlaceholderRenderingsArgs args)
        {
            Item placeholderItem;
            var placeholderKey = string.Join("/", args.PlaceholderKey.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Split('#')[0]));

            using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
                placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase, args.LayoutDefinition);

            List list = null;
            if (placeholderItem != null)
            {
                args.HasPlaceholderSettings = true;
                bool allowedControlsSpecified;
                list = GetRenderings(placeholderItem, out allowedControlsSpecified);

                if (allowedControlsSpecified)
                    args.Options.ShowTree = false;
            }

            if (list == null)
                return;

            if (args.PlaceholderRenderings == null)
                args.PlaceholderRenderings = new List();

            args.PlaceholderRenderings.AddRange(list);
        }
    }
}








Page Editor- Select Allow Controls 

TextImage Renderings



Thursday, January 7, 2016

Sitecore Web API for Angularjs MVC integration

Problem Statement
What if there is CMS system hosted in DMZ server and there is no scope of adding any additional requirement due to some reason and still wants to use Sitecore CMS as an option to port content to your digital interface that can be Mobile or other touch screen device installed in stores. Here is the solution to do so.
Recently we did the same implementation and made Sitecore CMS as source system to give data to digital screens and native Iphone App.

Cloud and Sitecore CMS way.
  1. Sitecore CMS hosted in DMZ corporate infrastructure. Expose api's to access required data.
  2. There will be asp.net spa applicaiton developed using asp.net MVC +angularjs and hosted in cloud to consume this CMS system.
Technology
  1. glass mapper 
  2. sitecore CMS 7.2
  3. asp.net web api
Sitecore CMS Items Implementation
We only going to have content hence i created just reference folder within sitecore structure as per screenshot. Remember to take item id GUID for category to lookup through glass mapper.

Create Website Content under reference. As this is not going to be website path page item.

Create Category page item with category template. Keep the Item ID -Guid for glass mapper mapping with object tree to fetch categories.

Template Category

Add content.




Asp.net Web Api Implementation

The sitecore data item will be consumed via glass mapper ORM and can be modelled to get through API controller.

Here is the snapshot of solution explorer

The reference of nuget glass mapper in include configuration. This helps in ORM with sitecore object mapping.


Create Web api Route with respect to Sitecore

using System.Net.Http.Formatting;
using System.Web;
using System.Web.Http;

namespace MvcApplication1.App_Start
{
    public class RegisterWebApiRoute
    {
        public void Process(PipelineArgs args)
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

        public void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            // Only JSON response
            config.Formatters.Clear();
            config.Formatters.Add(new JsonMediaTypeFormatter());
            /*
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            */
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace MvcApplication1.App_Start
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Controller Category to fetch Sitecore Category content


using Glass.Mapper.Sc;
using MvcApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;

namespace MvcApplication1.Controllers
{
    public class CategoryController : ApiController
    {
        public CategoryList categoryList { get; set; }
        public const string CATEGORY_ROOT_GUID = "{A547EA1E-D414-4521-80E0-A7C794BA3E8C}";
        
        // GET api/values
        [Route("root/api/category")]
        public List GetCategories()
        {
            //return new List();
            var context = new SitecoreContext();
            List categories = new List();
            categoryList = context.GetItem(CATEGORY_ROOT_GUID);
            var categoryBucket = categoryList.Children.Select(a => new Category() 
            { 
                CategoryId = a.CategoryId,
                Title = a.Title, 
                Description = a.Description, 
                SmallImage = a.SmallImage 
            }).ToList().Where(x => x.Title != null);
            foreach (var v in categoryBucket)
            {
                categories.Add(new CategoryItem() { 
                CategoryId=v.CategoryId,
                Title=v.Title,
                Description=v.Description,
                SmallImage=v.SmallImage
                });
            }
            return categories;
        } 
    }
}

Glass Mapper for Category : Children and Sitecore Parent

using Glass.Mapper.Sc.Configuration.Attributes;
using Glass.Mapper.Sc.Fields;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcApplication1.Models
{
    public class Category
    {
        [SitecoreId]
        public virtual Guid CategoryId      { get; set; }
        public virtual string Title         { get; set; }
        public virtual string Description   { get; set; }
        public virtual Image SmallImage     { get; set; }
    }
    public class CategoryItem
    {
        public  Guid CategoryId { get; set; }
        public  string Title { get; set; }
        public  string Description { get; set; }
        public  Image SmallImage { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcApplication1.Models
{
    public class CategoryList
    {
        public virtual IEnumerable Children { get; set; }
    }
}

Required Output.
Ensure you place the DLL in bin folder of sitecore website intance to keep this running.


Monday, January 4, 2016

Extend Custom Sort in Sitecore.

Implementation Steps

  1. Go to system ->settings-> subitems sorting create new item with template Sytem/Child Sorting
  2. Create below Demo.Sitecore.Practical.CustomSortComparer.dll and deploy in CMS instance bin folder.
  3. Add Type and class assembly details under Data section for Newly created item Created Date( instance of sitecore Setting Subitems sorting.
  4. You can sort using subitem sorting for any sitecore tree using Custom Created Date.


Class Library

using System;
using System;
using Sitecore.Data.Comparers;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Demo.Sitecore.Practical.CustomSortComparer
{
    public class SitecoreCustomDateFieldComparer: Comparer
    {
        protected override int DoCompare(Item item1, Item item2)
        {
            var date1 = GetDateTime(item1);
            var date2 = GetDateTime(item2);
            return date1.CompareTo(date2);
        }

       private static DateTime GetDateTime(Item item)
        {
            var dateField = (DateField)item.Fields["Date"];
            return dateField != null ? dateField.DateTime : DateTime.MinValue;
        }
    }
}