Commerce Computed Indexfield

Computed fields allow you to add entirely new fields in your search indexes, in addition to those of Sitecore.

To create a computed index field for commerce entities you need to follow next steps:

  1. Create your own class which inherits from : Sitecore.Commerce.Engine.Connect.Search.ComputedFields.BaseCommerceComputedField

2. Customize the class to meet your particular needs.

The “JToken entity” object contains few information about your sellableitem (in the below example is a sellableitem from SXA)

3. Add the Computed Field in the Configuration:

 

 

Advertisements

Sitecore Commerce Icons

Ten years ago when I started working with Sitecore, one of the first thing I learn it was how to select icons for items templates.

In Sitecore Commerce is a bit different how to choose icons for entity views.You don’t have a picker for icons.

Icons in Sitecore Commerce are stored in Bizfx tools in the root folder in SitecoreIcons.*.svg file.

bizfxtool

To view all the icons from the Bizfx toool you can use  http://fontello.com/

You can drag and drop and select the icons .

fontello

 

After you select the SVG file you will see icons available for Sitecore Commerce.

BizfxIcons

Using Fontello you can search for icons :

calendarclock

To create an entity view with a selected icons you have use :

var newEntityView = new EntityView
{
Name = “CustomEntityView”,
DisplayName = “CustomEntityView”,
Icon = “calendar_clock”,
ItemId = “CustomEntityViewId”
};

 

The result for on Commerce Dashboard is :

CustomEntityView

In the next blog I will explain how to add a new EntityView to CommerceDashboard

 

Field Validators on Experience Editor

On Sitecore 8 I added few fields validators.
Everything works perfectly on Content Editors but not on Experience Editor.

On config files we have next setting:


<!--
  WEB EDIT ENABLE VALIDATION
            If true, the Page Editor will execute item and field validation rules whenever a user tries to save items in the Page Editor.
            Only 'Critical' and 'Fatal' validators are evaluated, and item validation rules are executed for the current context item only.
            Field validation rules are only executed for fields that the current user can modify in the Page Editor.
            Default value: true
      
-->
<setting name="WebEdit.EnableValidation" value="true" patch:source="Sitecore.ExperienceEditor.config"/> 
 

but is ignored on Experience Editor.

I found a new solution switching Experience Editor from Speak UI to Sheer UI.
How to switch it :

Modify on App_Config\Include\Sitecore.ExperienceEditor.config
mvc.renderPageExtenders pipeline

1. Comment out the following line in the:

<pageextender type="Sitecore.ExperienceEditor.Speak.Ribbon.PageExtender.RibbonPageExtender, Sitecore.ExperienceEditor.Speak.Ribbon" />
[/soucecode]
2. Uncomment the following lines:
 
<pageextender type="Sitecore.Layouts.PageExtenders.PreviewPageExtender, Sitecore.ExperienceEditor" />
<pageextender type="Sitecore.Layouts.PageExtenders.WebEditPageExtender, Sitecore.ExperienceEditor" />
<pageextender type="Sitecore.Layouts.PageExtenders.DebuggerPageExtender, Sitecore.ExperienceEditor" />
 

If you have a Mvc Solution you need to change Sitecore.MvcExperienceEditor.config
1. Uncomment the page extenders below and comment the “SPEAK-based” Experience Editor ribbon processors to switch to old SheerUI-based Experience Editor ribbon.

 
        <processor type="Sitecore.Mvc.ExperienceEditor.Pipelines.RenderPageExtenders.RenderPageEditorExtender, Sitecore.Mvc.ExperienceEditor"></processor>
        <processor type="Sitecore.Mvc.ExperienceEditor.Pipelines.RenderPageExtenders.RenderPreviewExtender, Sitecore.Mvc.ExperienceEditor"></processor>
        <processor type="Sitecore.Mvc.ExperienceEditor.Pipelines.RenderPageExtenders.RenderDebugExtender, Sitecore.Mvc.ExperienceEditor"></processor>
    

2. Comment next pipeline

   <processor type="Sitecore.Mvc.ExperienceEditor.Pipelines.RenderPageExtenders.SpeakRibbon.RenderPageEditorSpeakExtender, Sitecore.Mvc.ExperienceEditor"></processor>
   

Goals in a multisite solution

Yesterday it was a question on Community how to implement Goals in a multisite solution: https://community.sitecore.net/developers/f/9/t/1913

And if I write few blog posts about multisite I said to write also a blogpost about implementing goals in a multisite solution.

My site structure is :

Countries

My goals structure is :

Step 1 :

Modify site definition like. I added a new property to sites “goalsFolder”

  <site name="countrythree" patch:before="site[@name='website']" hostName="countrythree.sitecoredemo.com" inherits="sitecoremvc-base" rootPath="/sitecore/content/countrythree" startItem="/" dictionaryDomain="{D8BC2E0C-36C5-4128-8F29-352B55E86676}" language="en" goalsFolder="countrythree" />

  <site name="countrytwo" patch:before="site[@name='website']" hostName="countrytwo.sitecoredemo.com" inherits="sitecoremvc-base" rootPath="/sitecore/content/countrytwo" startItem="/" dictionaryDomain="{D8BC2E0C-36C5-4128-8F29-352B55E86676}" language="en" goalsFolder="countrytwo" />


  <site name="countryone" patch:before="site[@name='website']" hostName="countryone.sitecoredemo.com" inherits="sitecoremvc-base" rootPath="/sitecore/content/countryone" startItem="/" dictionaryDomain="{D8BC2E0C-36C5-4128-8F29-352B55E86676}" language="de-de" goalsFolder="countrytwo" />

  <site name="sitecoremvc-base" patch:before="site[@name='website']" hostName="*" enableTracking="true" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content" startItem="/home" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB" filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" cacheRenderingParameters="true" renderingParametersCacheSize="10MB" />
  

Step2:
Change type of command with name “anylitics:opengoals” from Sitecore.Analytics.config
into:

  <command name="analytics:opengoals" type="Sitecore.Analytics.OpenGoals,SitecoreDemo" patch:source="Sitecore.Analytics.config"/>

Step3:
Create a new class OpenGoals:

    using Sitecore;
    using System;
    namespace SitecoreDemo.Analytics
    {
      [Serializable, UsedImplicitly]
      public class OpenGoals : OpenTrackingField
      {
        // Methods
        protected override string GetUrl()
        {
            return "/sitecore/shell/~/xaml/Sitecore.Shell.Applications.Analytics.TrackingField.Goals.aspx";
        }
       }
    }
    

4. Create a new xaml file name it Goals.xaml and add it to folder: /Sitecore/Shell/Override

      <?xml version="1.0" encoding="UTF-8" ?>
<xamlControls xmlns:x="http://www.sitecore.net/xaml" xmlns:ajax="http://www.sitecore.net/ajax" xmlns:rest="http://www.sitecore.net/rest" xmlns:r="http://www.sitecore.net/renderings" xmlns:xmlcontrol="http://www.sitecore.net/xmlcontrols" xmlns:p="http://schemas.sitecore.net/Visual-Studio-Intellisense" xmlns:asp="http://www.sitecore.net/microsoft/webcontrols" xmlns:html="http://www.sitecore.net/microsoft/htmlcontrols" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <Sitecore.Shell.Applications.Analytics.TrackingField.Goals x:inherits="SitecoreDemo.Analytics.GoalsPage,SitecoreDemo">

    <Sitecore.Controls.DialogPage runat="server" Header="Goals" Text="Select the goals that you want to associate with the selected item.">
      <AjaxScriptManager runat="server"/>
      <ContinuationManager runat="server" />
      <Script runat="server" Src="/sitecore/Shell/Applications/Analytics/TrackingField/TrackingField.js" />


<Style runat="server">
        #GoalsList table tr > td {
          padding: 0 0 10px 0;
        }
      </Style>




<table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">


<tr>


<td height="100%">
            <GridPanel Width="100%" Height="100%" runat="server" Background="White">

              <Border runat="server" GridPanel.Style="height:100%" Height="100%">
                <Scrollbox id="GoalsList" runat="server" Height="100%" Padding="0px"/>
              </Border>
            </GridPanel>
          </td>


        </tr>


      </table>


        
        
    </Sitecore.Controls.DialogPage>
  </Sitecore.Shell.Applications.Analytics.TrackingField.Goals>


</xamlControls>

    

5. Create a new class GoalsPage

using Sitecore;
using Sitecore.Analytics;
using Sitecore.Analytics.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Extensions.XElementExtensions;
using Sitecore.Shell.Applications.Analytics.TrackingField;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Xml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using SitecoreDemo.Util;

namespace SitecoreDemo.Analytics
{
   [UsedImplicitly]
public class GoalsPage : TrackingFieldPageBase
{
    // Methods
    protected override void OK_Click()
    {
        Packet packet = new Packet("tracking", new string[0]);
        base.AddIgnoreFlag(packet);
        base.AddExistingProfiles(packet);
        base.AddExistingCampaigns(packet);
        if (HttpContext.Current != null)
        {
            List<PageEventItem> pageEventDefinitions = new List<PageEventItem>(from item in Tracker.DefinitionItems.PageEvents.Concat<PageEventItem>(Tracker.DefinitionItems.Goals)
                where item.IsDeployed
                select item);
            base.AddExistingNoneGoals(packet, pageEventDefinitions);
            CheckBoxList checkBoxList = this.GoalsList.Controls[0] as CheckBoxList;
            if (checkBoxList != null)
            {
                TrackingFieldPageBase.GetEvents(packet, pageEventDefinitions, checkBoxList);
            }
            SheerResponse.SetDialogValue(packet.ToString());
            base.OK_Click();
        }
    }

    protected override void OnLoad(EventArgs e)
    {
        Assert.ArgumentNotNull(e, "e");
        Assert.CanRunApplication("Content Editor/Ribbons/Chunks/Analytics - Attributes/Goals");
        base.OnLoad(e);
    }

    protected override void Render(XDocument doc)
    {
        Assert.ArgumentNotNull(doc, "doc");
        this.RenderGoals(doc);
    }

    private void RenderGoals(XDocument doc)
    {
        Assert.ArgumentNotNull(doc, "doc");
        List<string> selected = new List<string>();
        foreach (XElement element in doc.Descendants("event"))
        {
            selected.Add(element.GetAttributeValue("name"));
        }
        CheckBoxList child = new CheckBoxList {
            ID = "GoalsCheckBoxList"
        };
        this.GoalsList.Controls.Add(child);
        System.Web.UI.Page page = TrackingFieldPageBase.GetPage();

        var site = ItemUtil.GetSiteContextForItem(ItemUtil.GetItem(Sitecore.Context.Request.QueryString["id"]));
        var goalsFolder = site.Properties["goalsFolder"];
        if ((page != null) && !page.IsPostBack)
        {
            IOrderedEnumerable<PageEventItem> query;
            if (!string.IsNullOrEmpty(goalsFolder) && Sitecore.Context.ContentDatabase.GetItem(Tracker.DefinitionItems.Goals.Path+"/"+goalsFolder)!=null)
            {
                query = from e in Tracker.DefinitionItems.AllPageEvents
                        where e.InnerItem.Parent.Key.Equals(goalsFolder, StringComparison.InvariantCultureIgnoreCase) && (e.IsDeployed && e.IsGoal) && !e.IsSystem
                        orderby e.DisplayName
                        select e;
            }
            else
            {
                query = from e in Tracker.DefinitionItems.AllPageEvents
                        where(e.IsDeployed && e.IsGoal) && !e.IsSystem
                        orderby e.DisplayName
                        select e;
            }
            TrackingFieldPageBase.RenderCheckBoxList(child, query, selected);
        }
    }

    


    // Properties
    [UsedImplicitly]
    protected Scrollbox GoalsList { get; set; }
}
}

Step6. Create a new class OpenTrackingField

    using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Text;
using Sitecore.Web;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;
using System;
using Sitecore.StringExtensions;

namespace SitecoreDemo.Analytics
{
    [Serializable, UsedImplicitly]
    public class OpenTrackingField : Sitecore.Shell.Applications.Analytics.TrackingField.OpenTrackingField
    {
        CommandContext context;
        // Methods
        public override void Execute(CommandContext context)
        {
            this.context = context;
            base.Execute(context);
        }

        protected virtual string GetUrl()
        {
            return "/sitecore/shell/~/xaml/Sitecore.Shell.Applications.Analytics.TrackingField.aspx";
        }

        [UsedImplicitly]
        protected void Run(ClientPipelineArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Item item = base.DeserializeItems(args.Parameters["items"])[0];
            if (SheerResponse.CheckModified())
            {
                string str = args.Parameters["fieldid"];
                if (string.IsNullOrEmpty(str))
                {
                    str = "__Tracking";
                }
                if (args.IsPostBack)
                {
                    if (args.HasResult)
                    {
                        using (new StatisticDisabler(StatisticDisablerState.ForItemsWithoutVersionOnly))
                        {
                            item.Editing.BeginEdit();
                            item[str] = args.Result;
                            item.Editing.EndEdit();
                        }
                        if (AjaxScriptManager.Current != null)
                        {
                            AjaxScriptManager.Current.Dispatch("analytics:trackingchanged");
                        }
                        else
                        {
                            Context.ClientPage.SendMessage(this, "analytics:trackingchanged");
                            Context.ClientPage.SendMessage(this, "item:refresh(id={0})".FormatWith(new object[] { item.ID.ToString() }));
                        }
                    }
                }
                else if (item.Appearance.ReadOnly)
                {
                    SheerResponse.Alert("You cannot edit the '{0}' item because it is protected.", new string[] { item.DisplayName });
                }
                else if (!item.Access.CanWrite())
                {
                    SheerResponse.Alert("You cannot edit this item because you do not have write access to it.", new string[0]);
                }
                else
                {
                    UrlString urlString = new UrlString(this.GetUrl());
                    urlString.Add("id", context.Items[0].ID.ToShortID().ToString());
                    UrlHandle handle = new UrlHandle();
                    handle["tracking"] = item[str];
                    handle.Add(urlString);
                    this.ShowDialog(urlString.ToString());
                    args.WaitForPostBack();
                }
            }
        }
    }
}
     

Step7.
I am on countryone site and I want to add a new goal to a page.
Is showing me just the goals from countryone folder.

OpenGoalCountry

This code was tested on Sitecore 8.1.

 

Configuring indexes in a multisite solution

In the previous blogpost I wrote about aliases and how to inherit sites from a base site.
Also for the lucene indexes is posible to do it.

We have a Sitecore solution with 3 country sites that have same structure.
(please see below picture)

Countries

We need to create elegant solution for creating indexes for every country.

First step is create base configuration, I create a config file (Sitecore.ContentSearch.Lucene.Index.ZZZConfiguration.config ) that I add into a “ZZZ” folder to be the last in configuration file.

    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <contentSearch>
      <configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch">
        <indexTemplates hint="skip">
          <searchIndexTemplate id="$(1)" type="Sitecore.ContentSearch.LuceneProvider.SwitchOnRebuildLuceneIndex,Sitecore.ContentSearch.LuceneProvider">
            <param desc="name">$(1)</param>
            <param desc="folder">$(1)</param>
            <!-- This initializes index property store. Id has to be set to the index id -->
            <param desc="propertyStore" ref="contentSearch/indexConfigurations/databasePropertyStore" param1="$(id)" />
            <strategies hint="list:AddStrategy">
              <!-- NOTE: order of these is controls the execution order -->
              <strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/rebuildAfterFullPublish" />
              <strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/onPublishEndAsync" />
              <strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/remoteRebuild" />
            </strategies>
            <commitPolicyExecutor type="Sitecore.ContentSearch.CommitPolicyExecutor, Sitecore.ContentSearch">
              <policies hint="list:AddCommitPolicy">
                <policy type="Sitecore.ContentSearch.TimeIntervalCommitPolicy, Sitecore.ContentSearch" />
              </policies>
            </commitPolicyExecutor>
            <locations hint="list:AddCrawler">
              <crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
                <Database>$(2)</Database>
                <Root>$(3)</Root>
              </crawler>
            </locations>
            <configuration ref="contentSearch/configuration/defaultIndexConfiguration | contentSearch/configuration/DefaultIndexConfiguration | contentSearch/indexConfigurations/defaultLuceneIndexConfiguration">
              <documentBuilderType>Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilder, Sitecore.ContentSearch.LuceneProvider</documentBuilderType>
              <IndexAllFields>true</IndexAllFields>
              <exclude hint="list:ExcludeTemplate">
                <patch:delete />
              </exclude>
                <include hint="list:IncludeTemplate">
                 <!--add here your templates --> 
              </include>
              <fields hint="raw:AddComputedIndexField">
                 <!--add here your computed index field --> 
              </fields>
 
             </configuration>
          </eventIndexTemplate>
        </indexTemplates>
      </configuration>
    </contentSearch>
  </sitecore>
</configuration>
    

Now we need to create index config file that inherit from above configuration

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
      <contentSearch>
         <configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch">
        <!--searchj-->
        <indexes hint="list:AddIndex">
          <index ref="contentSearch/configuration/indexTemplates/searchIndexTemplate" param1="country1" param2="web" param3="/sitecore/content/countryone" />
          <index ref="contentSearch/configuration/indexTemplates/searchIndexTemplate" param1="country2" param2="web" param3="/sitecore/content/countrytwo" />
	  <index ref="contentSearch/configuration/indexTemplates/searchIndexTemplate" param1="country3" param2="web" param3="/sitecore/content/countrythree" />
        </indexes>
      </configuration>
    </contentSearch>
  </sitecore>
      </configuration>
   

 
You can see I replace $(1),$(2),$(3) from first config file with parameters from second config file.
These are indexes for web database, if we want to create also for master database we need to modify strategy section
Above is the result of our configuration:

Sitecoreindexes

Configuring Aliases in a multi site solution

On the last Sitecore solution I have worked I needed to add aliases for every site.

Default aliases are shared for all sites from the solution.

To acomplish requirements we create under the item  /sitecore/system/Aliases folders for every site.

SitecoreAliases

On site definition for every site we add a new property named alias

<site name=”countrytwo” patch:before=”site[@name=’website’]”
hostName=”countrytwo.sitecoredemo.com”
inherits=”sitecoremvc-base”
rootPath=”/sitecore/content/countrytwo”
startItem=”/”
dictionaryDomain=”{D8BC2E0C-36C5-4128-8F29-352B55E86676}”
language=”en”
alias=”countrytwo”
mvcArea=”countrytwo”

/>

We rewrite AliasResolver to map aliases to sites .

We need to rewrite the entire class Sitecore.Pipelines.HttpRequest.AliasResolver because the methods are private.

   
///<summary>
/// Custom Alias Resolver
/// </summary>


public class AliasResolver : HttpRequestProcessor
{
     ///<summary>
     /// Processes the specified args.
     /// </summary>
     /// <param name="args">The args.</param>
     public override void Process(HttpRequestArgs args)
     {
        Assert.ArgumentNotNull(args, "args");
        //check if aliases are active
        if (!Settings.AliasesActive)
        {
          Sitecore.Diagnostics.Log.Warn("Aliases are not active.",this);
          return;
         }
         Database database = Context.Database;
         if (database == null)
          {
            Sitecore.Diagnostics.Log.Warn("Context database is null",this);
            return;
          }

          if (database.Aliases.Exists(MainUtil.DecodeName('/' +                     Context.Site.Properties["alias"] + '/' + args.LocalPath)) &&                   !this.ProcessItem(args))
           {
              this.ProcessExternalUrl(args);
           }
       }

        ///<summary>
       /// Processes the external URL.
       /// </summary>
       /// <param name="args">The arguments.</param>
        private void ProcessExternalUrl(HttpRequestArgs args)
        {
          string targetUrl = Context.Database.Aliases.GetTargetUrl('/' +    Context.Site.Properties["alias"]+'/' +                                         args.LocalPath);
          if (targetUrl.Length > 0)
            {
              this.ProcessExternalUrl(targetUrl);
            }
         }

         ///<summary>
         /// Processes the external URL.
         /// </summary>
         /// <param name="path">The path.</param>
         private void ProcessExternalUrl(string path)
            {
              if (Context.Page.FilePath.Length > 0)
               {
                 return;
               }
                Context.Page.FilePath = path;
             }

          ///  <summary>
          /// Processes the item.
          /// </summary>
          /// <param name="args">The arguments.</param>
          /// <returns></returns>
          private bool ProcessItem(HttpRequestArgs args)
           {
             ID targetID = Context.Database.Aliases.GetTargetID(MainUtil.DecodeName('/'  + Context.Site.Properties["alias"]                     + '/' + args.LocalPath));
               if (!targetID.IsNull)
                {
                  Item item = args.GetItem(targetID);
                  if (item != null)
                   {
                    this.ProcessItem(args, item);
                   }
                   return true;
                  }
               return false;
             }

          ///<summary>
          /// Processes the item.
          /// </summary>
          /// <param name="args">The arguments.</param>
          /// <param name="target">The target.</param>
           private void ProcessItem(HttpRequestArgs args, Item target)
            {
              if (Context.Item == null)
              {
                 Context.Item = target;
               } 
                    }
       }

We replace default AliasResolver with new one, using a patch file:

   <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"     xmlns:set="http://www.sitecore.net/xmlconfig/set/">
      <sitecore>
         <pipelines>
          <httpRequestBegin>
             <processor type="Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel">
              <patch:attribute name="type">Namespace.AliasResolver,Assembly</patch:attribute>
            </processor>
           </httpRequestBegin>
          </pipelines>
      </sitecore>
  </configuration>   
  

Next blog will be about how to create lucene indexes in a multisite solution.

How to set up a multi site solution in sitedefinition

This blog post is the start for a series o blog posts about implementing multisite solution with Sitecore.

Starting from version 6.4 in web.config we have a new attribute for sites in site definition

inherits: Indicates that the attributes should be inherited from another site. To enable inheritance, you must specify the name of the source site.
Attributes that are explicitly specified overwrite the attributes that are inherited from the source site.

We have a solution with 3 sites like in below picture:

Countries

For a multi site solution we are defining a base website and we inherit from it.

First declare base website

<site name=”sitecoremvc-base”
patch:before=”site[@name=’website’]”
hostName=”*”
enableTracking=”true”
virtualFolder=”/”
physicalFolder=”/”
rootPath=”/sitecore/content”
startItem=”/home”
database=”web”
domain=”extranet”
allowDebug=”true”
cacheHtml=”true”
htmlCacheSize=”50MB”
registryCacheSize=”0″
viewStateCacheSize=”0″
xslCacheSize=”25MB”
filteredItemsCacheSize=”10MB”
enablePreview=”true”
enableWebEdit=”true”
enableDebugger=”true”
disableClientData=”false”
cacheRenderingParameters=”true”
renderingParametersCacheSize=”10MB” />

and now we need to declare country sites:

<site name=”countrythree” patch:before=”site[@name=’website’]”
hostName=”countrythree.sitecoredemo.com”
inherits=”sitecoremvc-base”
rootPath=”/sitecore/content/countrythree”
startItem=”/”
dictionaryDomain=”{D8BC2E0C-36C5-4128-8F29-352B55E86676}”
language=”en”
alias=”countrythree”
mvcArea=”countrythree”
/>

<site name=”countrytwo” patch:before=”site[@name=’website’]”
hostName=”countrytwo.sitecoredemo.com”
inherits=”sitecoremvc-base”
rootPath=”/sitecore/content/countrytwo”
startItem=”/”
dictionaryDomain=”{D8BC2E0C-36C5-4128-8F29-352B55E86676}”
language=”en”
alias=”countrytwo”
mvcArea=”countrytwo”

/>
<site name=”countryone” patch:before=”site[@name=’website’]”
hostName=”countryone.sitecoredemo.com”
inherits=”sitecoremvc-base”
rootPath=”/sitecore/content/countryone”
startItem=”/”
dictionaryDomain=”{D8BC2E0C-36C5-4128-8F29-352B55E86676}”
language=”de-de”
alias=”countryone”
mvcArea=”countryone”
/>

In my site definition  I have alias attribute for everysite, I will write about it “How to set up aliases in a multisite solution.