27 January 2015

Get related content by profile keys

So this posts is following directly the previous post.

With this 2nd posts I wanted to illustrate how you could use profile card to retrieve related content. During the Sitecore DMS fundamentals training session, we had a chat about Profile card, Profile keys and how we are assigning those to a content item. The example given there was for a Cycling Shop. We could create some profile keys for "Beginner", "Amateur" and "Professional". Obviously, you will have different product that will be for Professional: Helmets, Bikes and even GoPro (why not...). On previous project, we had similar cases where you then wanted to display related content so when a visitor is viewing a bike he will be display with an helmet and other item that will be tagged with the same category: "Professional". Well we usually create a tree list ex field and let the content author tag the content with the category. Even if I think this is great, I was wondering if we could use DMS and profile for that purpose as we are kind of tagging the content item with a card or a key... So here we go.

Before starting, I just wanted to point you to a few blogs and posts which helped me a lot:
So on the previous post we saw that the Tracking and profile cards information where stored on the item: field = __tracking. Awesome, that means we can index those data and so later on we will be able to retrieve it based on a content profile key...

1- Let's start with indexing content including _tracking

As per the posts listed above, the easiest way to index those data will be to use a computed field. In your search configuration file, you will need to add the following into your configuration section:

       
        
        < fields hint="raw:AddComputedIndexField">
          < field fieldName="dmstracking" returnType="stringCollection">MyProject.Business.Sitecore.Indexing.DmsComputedField,MyProject.Business< /field>
        < /fields>


Now in your solution you want to create the matching class MyProject.Business.Sitecore.Indexing.DmsComputedField

This class should implement the interface: Sitecore.ContentSearch.ComputedFields.IComputedIndexField

You will create something simple such as :

using Sitecore.Analytics.Configuration;
using Sitecore.Analytics.Data;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.ComputedFields;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using System.Collections.Generic;
using System.Linq;

namespace MyProject.Business.Sitecore.Indexing
{
    public class DmsComputedField : IComputedIndexField
    {
        public string FieldName
        {
            get;
            set;
        }
 
        public string ReturnType
        {
            get;
            set;
        }

        public DmsComputedField()
        {
        }
 
        public object ComputeFieldValue(IIndexable indexable)
        {
            Item item = indexable as SitecoreIndexableItem;
            List profileKeysNames = DmsHelper.GetProfileKeyNames(item,1).ToList();
            return profileKeysNames;
        }
    }
}


Now depending on what you want to index: Profile card Name or Profile Keys, you will need to fill in with your business logic. In this example we will index the list of Profile Keys name. Also note that on the configuration above, we are returning a stringCollection... Using the helpers we have created on the previous post we can retrieve the profile card names doing the following

            List< string> profileKeysNames = DmsHelper.GetProfileKeyNames(item,1).ToList();

Now our search index is configured, we can run the indexer and inspect the index using Luke:


2- Lets start retrieving the related item based on Keys

When the visitor visit a page, which has a profile (profile keys) associated to it, we can get those information through the helper we had on the previous post. from the list of keys for this item, we will be able to run a search against the dmstracking field..

            List< string> profileKeysNames = DmsHelper.GetProfileKeyNames(item,1).ToList();

So we can create a new controller rendering to execute our search


namespace MyProject.Controllers
{
    public class SearchController : SitecoreController
    {
        private readonly ISitecoreContext SitecoreContext;

        public SearchController(ISitecoreContext sitecoreContext)
        {
            this.SitecoreContext = sitecoreContext;
        }

        /// < summary>
        /// This controller action is used to get the related items
        /// < /summary>
        /// < returns>< /returns>
        [System.Web.Mvc.HttpGet]
        public ViewResult GetRelatedItems()
        {
            var searchResultModel = new SearchResult();

            // get the profile cards tagged on the content
            Item item = SitecoreContext.GetCurrentItem< item>();
            List< string> profileKeysNames = DmsHelper.GetProfileKeyNames(item, 1).ToList();

            // get the number of results per page - get only the top 5
            int nbItemPerPage = 5;
            searchResultModel.ResultsPerPage = nbItemPerPage;

            ISearchIndex contentIndex = ContentSearchManager.GetIndex("MyProject_contentsearch_web");
            using (IProviderSearchContext searchContext = contentIndex.CreateSearchContext())
            {
                var listResults =
                    searchContext.GetQueryable< basesearchitem>().Where(p =>
                        profileKeysNames.Contains(p.DmsTracking)
                                ).AsEnumerable().Distinct(new ItemComparer()).ToList();

                searchResultModel.TotalNbResults = listResults.Count();
                searchResultModel.Pages = (int)Math.Ceiling((Decimal)listResults.Count / nbItemPerPage);
                searchResultModel.Results = listResults.Take(nbItemPerPage).ToList();
            }

            return View(@"~/Resources/MyProject/Views/Renderings/Search/SearchResults.cshtml", searchResultModel);
        }
    }
}

There we go, yo can now retrieve all related items which have the same associated Profile Keys...

Also this code was just put together quickly to show some example and surely can be improved but it was just to give a quick start...

14 January 2015

Few steps torward automated deployment


A know a few people will be in favour of "You need to setup the full automated deployment right away"... Well, I don't think this is that easy, you have to look at your timeframe to deliver the project, the client budget, infrastructure and all the tools you have. So I was looking a bit into a starting point. Tis might not be the best method in the world, but I needed to get something started then get it grow and one day we will get a fully CI...

1- Get TDS to generate my .Update packages:

In a few projects now, I have been using TDS to generate my update packages to deploy to Internal QA, UAT and production. First we started to deploy those manually which is the first step: we already saving a bit of time not having to generate those packages manually.

2- Build server

Then we have been using a build server - awesome: now the packages will be generated by the build server... Better than my dev machine :)

3- Copying the packages to the relevant Instance

Well, I came short in this area but lucky there are few guys here who could help out. So we have been using Jenkins to take the output packages from TDS and place it into a folder on the Internal QA server. In this sitecore instance, we have created a handler (ashx) that will:
  • Look for the packages in this location
  • Serialise the content tree to make sure we have a backup before installing the packages
  • Backup the files
  • Install the packages.
  • Publish the items
  • Rebuild the indexes
I just wanted to throw some code we used for the different actions so if you are interested in, you can have a starting point

Serialising the tree:
well that is the easy bit - for this code sample I will take the entire tree:

using Sitecore.Data;
using Sitecore.Data.Items;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyProject.sitecore_modules.MyProject
{
    public partial class Backup_Site : System.Web.UI.Page
    {
        string SitecoreRootItem = "{11111111-1111-1111-1111-111111111111}";

        protected void Page_Load(object sender, EventArgs e)
        {
            BackupItemTree(SitecoreRootItem);
        }

        public void BackupItemTree(string id)
        {
            Database db = Sitecore.Configuration.Factory.GetDatabase("master");
            Item itm = db.GetItem(id);

            Sitecore.Data.Serialization.Manager.DumpTree(itm);
        }
    }
}



As I said, it is not the best, but still it is a first step to save time during deployment and a step forward in the Continuous Integration.