Friday, December 28, 2018

Lost in Translation - Building Your Language and Site Context from Scratch

This post is somewhat of a sequel to my previous post around multi-site and multi-language setup. When there is only one site and only one language things are pretty straightforward in terms of site context. Once multiple sites are introduced every time we are getting an item in Sitecore and/or building a link we need to make sure we do it within the correct site and correct language. For the most part Sitecore does it out of the box but there are cases when this functionality breaks down.

Here's an example. Suppose we have a multi-level navigation module that we assemble in the following manner:

1. an ajax call is made to the backend
2. backend method gets the item hierarchy from Sitecore
3. backend method returns serialized items as Json
4. in our java script code we generate navigation html with all the data we just got from Sitecore

Sounds easy, right? Not quite. By the time we arrive at step 2 we are stuck with null values for any item we try to get. We have lost our site context.

Let's bring it back! First thing to do is to pass some values from the view along with the ajax call. This could be done multiple ways. Below is just one way of doing so. We will add hidden values that would carry things like current item id, context database and language.

<input type="hidden" id="currentItem" value="@Sitecore.Context.Item.ID" />
<input type="hidden" id="currentDb" value="@Sitecore.Context.Database" />
<input type="hidden" id="currentLanguage" value="@Sitecore.Context.Site.Language" />

Then in the ajax call we need to make sure we pass these values to our backend method. Read the values first:

var currentItem = document.getElementById('currentItem').value;
var currentDb = document.getElementById('currentDb').value;
var currentLan = document.getElementById('currentLanguage').value;

and send them on their way:

var urlString = "yourUrlHere?item=" + currentItem + "&db=" + currentDb + "&lan=" + currentLan;

url: urlString......
and so on

And now we can rebuild our context and get what we need from the sitecore tree making sure we grab a correct site and a correct language.

public ActionResult GetJson()
//read query string params from the url
string currentItem = Request.QueryString["item"];
string currentDb = Request.QueryString["db"];
string currentLan = Request.QueryString["lan"];

// get current database, get current item and its pathvar db = Sitecore.Configuration.Factory.GetDatabase(currentDb);
Item item = db.GetItem(currentItem);
string itemPath = item.Paths.FullPath;

//build sitecore context
SiteInfo site = SiteContextFactory.Sites
.Where(s => s.RootPath != "" & itemPath.StartsWith(s.RootPath, StringComparison.OrdinalIgnoreCase))
.Where(s => s.Language == currentLan)
.OrderByDescending(s => s.RootPath.Length)

var website = new SiteContext(site);

// now that we have our context let's throw in some url options that you might need depending on how you want it set up

    using (new SiteContextSwitcher(website))
        var options = LinkManager.GetDefaultUrlOptions();
        options.AlwaysIncludeServerUrl = true; // could be false, depends on requirements
        options.Site = new SiteContext(site);
        options.SiteResolving = true;
        options.LanguageEmbedding = LanguageEmbedding.Never; // could be Always or As Needed   depending on requirements

       // from here on we can get our items in the proper context and language and build links using the url options we just built. And of course serialize it all and return as json.

Friday, December 14, 2018

Sustainable Growth: Going Hybrid for a Multi-site and Multi-lingual Sitecore setup

As a website evolves and its audience grows making your content available in multiple languages becomes a top priority. The most common way to achieve this goal is to use Sitecore's out of the box language versioning functionality where the tree is shared and the same item in can have translated versions. But what if it's not only your content that differs between various country sites but also presentation? What if your sites intended for various countries are independent from each other and possibly share some components but not all? What you can do is set up a site in such a way that tree is split between countries meaning each site has its own site definition with it's own hierarchy.

Now let's consider a more sophisticated arrangement where not only we have several country sites but each site needs to have content in multiple languages. Also, country sites need to be independent from one another and have their own site settings and their own hierarchy. In addition to these requirements we are also asked to maintain the same host name for all our global sites.

What are they thinking? They are asking for the moon! Not really. What our stakeholders are asking is a hybrid approach that will allows us to combine shared tree with split tree setup.

Let's walk through an example. Our company XYZ Incorporated is going through an expansion and we are building a site for a global audience. For now let's add a Japanese site keeping in mind that we plan to have content for Japan in two languages: Japanese and English. Obviously, the English version of the Japanese site would be different from the English version of our US site, therefore we cannot reuse the US node as a starting point.

In order to accommodate this we have to create a custom culture for English in Japan. You can install it on your system by using C# code or run a Powershell script for example. Once it's available we can add a new language under System Languages node in sitecore using our new culture.

What you see highlighted in red is English (Japan). It can be represented with /en-us in our url.

Our next step is to add site definitions for each country (familiar task) but wait, because we need multiple languages for multiple countries we need to use language-culture to determine a site. What do I mean? 

We create two site definitions for each of our language-cultures. In other words we would have a site and site both using the same host name with either /ja-jp or /en-jp in the url to indicate a language. We need to make sure we add virtualFolder and physicalFolder to our site definitions using language-culture combination. But also indicate a language separately. 

I know, it's getting complicated but believe me in the end the XYZ corporation will have a beautiful and versatile site their global audience would love!

Let's continue. We created nodes, we added site definitions for each language in each country. Are we done? Almost. Now we need to add a config patch file and set AlwaysStripLanguage setting to false. 

Now we are done and the content team can start building pages for our brand new Japanese site and use content in Japanese as well as English languages.

Tuesday, October 30, 2018

What to look forward to with Sitecore 9.1

We all heard about it. We all read about it. We are all excited about it! No, I am not talking about a reason why Ariana Grande broke up with her boyfriend. I am talking about a long awaited Sitecore 9.1’s SIF overhaul! Is it going to be a one-click install? No. But close. SIF 2.0 will install most of the required prerequisites automatically. Imagine that!

SIF 2.0 is only one of the features Sitecore community has been waiting for. Data Exchange Framework, even though not a brand-new addition, has seen expansion and improvements between earlier versions and 9.1. For instance, integration with Salesforce Marketing Cloud will support bi-directional exchange of experience data such as persona information and click events. This data can now be used, for example, to trigger email campaigns within Salesforce Marketing Cloud.

Sitecore headless solves a major problem for Front End developers. They can now develop and deploy their code to any platform in a headless configuration using their own languages and frameworks of choices. Their solution can be fully disconnected from Sitecore and be self-contained which allows easy management and deployment. Front End developers can have access to pure content without all the overhead that comes with a Sitecore installation. API only approach to web content management is utilized in Commerce where services are supported through the API and delivered vis JSS.

Universal Tracker is a new player in the headless innovation. It is a tracking service based on Web/Rest API technology allowing to build headless applications without sacrificing Sitecore’s personalization, analytics, and A/B testing features.

If you are looking for a quicker way to launch a Sitecore website you are of course familiar with  Sitecore Experience Accelerator or SXA. In 9.1 SXA offers WCAG 2.0 compliant websites creation along with wireframes and gray scale modes.

In Sitecore 9.1 authentication has had a major refactor. All authentication in Sitecore 9.1 is handled by a separate standalone .NET Core application. This .NET core app is based on the IdentityServer4 framework and supports Single Sign-On with OpenID.

9.1 brings expansion of deployment options for your xDB. Deploy it on Microsoft SQL Server, Microsoft SQL Azure as well as MongoDB if you are on the schema-less side.

Various  Sitecore Forms enhancements also became available with 9.1 For example, you can add conditions to form elements. Which means you can create a conditional logic based on user’s inputs on the form and hide/show components based on user’s interactions.

Last but not least is long-awaited  Machine Learning. Architected via integration with Microsoft Machine Learning Server it allows flexible integration with other ML engines and algorithms such as Salesforce Marketing Cloud, Einstein, or IBM Watson. One of the latest features in Sitecore Machine Learning is Content Tagging that creates information taxonomies used in automated personalization recommendations.

Sitecore 9.1 also delivers miscellaneous improvements in Content Search, Core architectural changes, scalability and infrastructure improvements. So much to look forward to!

Wednesday, March 14, 2018

Sitecore + Async != Nightmare

This is a sequel to my previous blog post on controller renderings in Sitecore and MVC views ( My next challenge was to make async calls. The key to this functionality is registering each route involved in async calls separately. Let's look at all the pieces of the puzzle.

In my view I have a label which displays my email. Next to it there's a button that opens up a text box where you can enter a new email.

In my 'Account' controller I have a method called 'UpdateEmail' that is reading a query string parameter called 'email'. This method records the new email in the database.

Upon a click of the Save button a block of javascript code invokes this method asynchronously:

var urlString = "/account/updateemail/?email=" + model.Email;

$.getJSON(urlString, function (data) 
      // Do your stuff here such as hide the div 
      // with the text box and Save and Cancel buttons.

When testing I was getting a 404 error which was not surprising because with Sitecore I need to have a navigable item with a controller rendering on it that points to my account controller and the 'UpdateEmail' method.

I tried registering this route individually and it worked! In the RegisterRoutes class, inside Register method I added the following:

routes.MapRoute("AccountUpdateEmail""account/updateemail"new {controller = "Account", action = "UpdateEmail"});

I think this is a small price to pay for having a way to do async calls with sitecore: register each route involved in those calls separately.