Experience Sitecore ! | All posts tagged 'Basics'

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

How websites are resolved with Sitecore - the essentials

What actually happens when you type website URL in browser and how is your request factually served in sitecore. We are going to use www.site.com as the URL for our example. This is simplified version where only most important steps are covered.

First of all, as you hit this URL in browser's address bar, it retrieves IP address by the host name from DNS server. Then it creates request to IP address resolved with host name (www.site.com) in HTTP header. There may be multiple traffic managers / load balancers on the route to the server, but eventually request gets to that IP address to the specified port (as we use normal HTTP, then the port is default 80).

On the server computer there should be specific software running and listening to that port (80) otherwise request will fail. In our scenario, that is the Microsoft IIS web server. Below are two most important IS configuration screens:


On the first screenshot you see Site Bindings screen that binds exact website within current IIS instance to specific port and hostname (if set). There can be multiple website hosted within same IIS instance, so you we usually vary them by hostname / port combinations. The example above shows that all requests to port 80 with the hostname www.site.com would be served by current website. Second record shows that all requests to port 443 (which is default to HTTP) would be served by the same site as well.


The second screenshot assigns our website www.site.com to a folder on a disk drive which becomes a web root for our site.

Root of the website should have a configuration file called web.config, that may be split and overridden in subfolders and that itself overrides global web.config and machine.config files with default settings. Every sitecore-based website contains <sitecore> node in the configuration file, that is where all sitecore settings are defined, including <sites> node that specifies all the websites per current Sitecore instance.


Important to note that sites are determined by "first match" principle, so order is critical. If you look at our www.site.com record - you'll notice that it specifies hostname - all requests matching that name will be served by this site. Other setting set what database is used for particular site (name should match database name from connection string), what is starting sitecore tree node within that database - factual page item that is being returned. There are also html caching setting, sitecore domain name for the site, etc.

The rest of request to current Sitecore instance, that do no math our host name pattern will be served by site called "website", it does not have hostName specified, so it will serve everything else and return /sitecore/content/home item.

Let's assume the site has been published from master database to web database, as per configuration. Here's below how sitecore tree looks like in Sitecore Content Editor:


Our website's home item (selected) is called SiteCom and it will return the page with "www.site.com"in title. Let's see the browser:


Here we done. That is our www.site.com landing page loaded!


Advanced topic: How to host several sites within the same Sitecore instance without specifying a hostname, just on different ports


[Updatable] API snippets

This blog post is [Updatable] - I use it to store snippets at the same place just for a case.

I know for most of experienced Sitecore geeks my post may seem sort of obvious, however even you may find something interesting below or suggest me to add anything you consider important to append.

While working with Sitecore for years, I have met so many attempts to re-invent the wheel in sense of ugly implementation of functions that are part of Sitecore API, so I decided to write this post, placing most frequent but sometimes less known usages here within one post. So, here we go:

  1. Standard API - create, read, edit, save, rename,
  2. Media library
  3. Publishing
  4. Presentation / placeholders
  5. Indexing
  6. DMS / xDB
  7. Other


1. Standard API - create, read, edit, save, rename, move etc.


1.1. To start with the basics - how to get the item. 

Item item = Sitecore.Context.Database.GetItem("/sitecore/content/home/products/tv");

if (item != null)             
{                 
    // allways check for null before going forward
}
Avoid using explicit database by name like below, get database out of Context, rather than by name explicitly.

// incorrect, except when you need explicitly to get CM database.
var database = Database().GetDatabase("master")); 


1.2. Accessing the fields - multiple ways:

// Indexer on Item supports using a field's name, index, or ID
string value1 = someItem[fieldName];
// Get the value from the field
string value2 = someField.Value;
// Method 3: 
bool allowStandardValue = false;
string value3 = someField.GetValue(allowStandardValue);
bool allowStandardValue = false;
bool allowDefaultValue = false;
string value5 = someField.GetValue(allowStandardValue, allowDefaultValue);

The order usually follows: if actual value exists, it is returned, otherwise if the item is clone - it returns clone's value, if not - returns standard value, and in case standard value not set - the default value (empty string, otherwise - null).


1.3. Read all fields.

For the sake of performance, Sitecore will not give you all fields in the FieldCollection in the following code, only fields with explicit values on item level, including empty string.

Sitecore.Context.Item.Fields.ReadAll();


1.4. Checking if a Sitecore field has a value

Sitecore.Context.Item.Fields["fieldName"].HasValue
or
Sitecore.Context.Item.Fields["fieldName"].Value != ""


1.5. When rendering fields from within the code, always use FieldRenderer class, in order to ensure that resulting output HTML  is Page Editor friendly.

FieldRenderer.Render(item, "field name")


1.6. Editing the item - straightforward way.

item.Editing.BeginEdit();
item.Fields["Title"].Value = "My New Title";
item.Editing.EndEdit();


1.7. More elegant way of editing the item, using IDisposable EditContext

using (new EditContext(item)) 
{ 
    item["Title"] = "My New Title"; 
}

More about EditContext and its options please read in API - IDisposable Usings article.


1.8. Miltilist field. I have evidenced several attempts of manipulation multi-list items by physically stored pipe-separated GUIDs, all clumsy and not best practice code with bad smell. Don't do that - use Sitecore API instead:

Sitecore.Data.Fields.MultilistField multilistField = Sitecore.Context.Item.Fields["myMultilistField"];
Sitecore.Data.Items.Item[] items = multilistField.GetItems();


1.10. Create item programmatically

Minimum code:
Item newItem = parent.Add(itemName, template); 
newItem.Editing.BeginEdit(); 
newItem.Fields["fieldName"].Value = "fieldValue"; 
newItem.Editing.EndEdit();
Full ready-to-use-code:
       public static void CreateItem()
        {
            // The SecurityDisabler overrides the current security model, allowing you
            // to access the item without any security. It's like the user being an administrator
            using ( new Sitecore.SecurityModel. SecurityDisabler())
            {
                // Get the master database
                Sitecore.Data. Database master = Sitecore.Data.Database.GetDatabase("master" );
                // Get the template to base the new item on
                TemplateItem template = master.GetItem("/sitecore/templates/Ecosystem/Concrete/MenuPage" );

                // Get the place in the site tree where the new item must be inserted
                Item parentItem = master.GetItem( "/sitecore/content/Ecosystem");

                // Add the item to the site tree
                Item newItem = parentItem.Add( "My New Page", template);

                // Set the new item in editing mode
                // Fields can only be updated when in editing mode
                // (It's like the begin tarnsaction on a database)
                newItem.Editing.BeginEdit();
                try
                {
                    // Assign values to the fields of the new item
                    newItem.Fields[ "Title"].Value = "My New Page - Title" ;
                    //newItem.Fields["Text"].Value = "Value2";

                    // End editing will write the new values back to the Sitecore
                    // database (It's like commit transaction of a database)
                    newItem.Editing.EndEdit();
                }
                catch (System. Exception ex)
                {
                    // The update failed, write a message to the log
                    //Sitecore.Diagnostics.Log.Error("Could not update item " + newItem.Paths.FullPath + ": " + ex.Message, this);

                    // Cancel the edit (not really needed, as Sitecore automatically aborts
                    // the transaction on exceptions, but it wont hurt your code)
                    newItem.Editing.CancelEdit();
                }
            }           
        }


1.11. Item propose name - will suggest valid item name according with current settings

ItemUtil.ProposeValidItemName("some-name");


1.12. Item has plenty of helpful methods, like CopyTo() and MoveTo() and others you may see in the context menu.

item.MoveTo (parentItem);



2. Media Library


2.1. Determining item type.

// usual content data item
Sitecore.Context.Item.Paths.IsContentItem;
// media item
Sitecore.Context.Item.Paths.IsMediaItem

2.2. The example below is anti-pattern. At the first glance it seems to be an elegant way of inline resolving media path right in MVC Razor view. So what is wrong with that?

 <img src="@MediaManager.GetMediaUrl(
Sitecore.Context.Database.GetItem(Html.Sitecore().CurrentRendering.DataSource))" />

First and most important - if datasource is not set (or set wrongly) then GetItem will return null being passed into GetMedaiUrl as the parameter, so it will fire NullReferenceException and the whole page won't load. You must validate parameters and do null-checks, so the best way out is to extract this logic into helper static (or extensions) class.

But the best option for sure is to pass this resolved URL into a view as a public property of strongly-typed viewmodel object.



3. Publishing


3.1. Publish item programmatically

private void PublishItem(Sitecore.Data.Items.Item item)
{
  // The publishOptions determine the source and target database,
  // the publish mode and language, and the publish date
  Sitecore.Publishing.PublishOptions publishOptions =
    new Sitecore.Publishing.PublishOptions(item.Database,
                                           Database.GetDatabase("web"),
                                           Sitecore.Publishing.PublishMode.SingleItem,
                                           item.Language,
                                           System.DateTime.Now);  // Create a publisher with the publishoptions
  Sitecore.Publishing.Publisher publisher = new Sitecore.Publishing.Publisher(publishOptions);

  // Choose where to publish from
  publisher.Options.RootItem = item;

  // Publish children as well?
  publisher.Options.Deep = true;

  // Do the publish!
  publisher.Publish();
}



4. Presentation / placeholders


4.1. Get count of renderings in a particular placeholder:

public static int GetRenderingsCount(string placeholderKey)
{
  return Sitecore.Context.Item.Visualization.GetRenderings(Sitecore.Context.Device, false).Count(p => p.Placeholder.Equals(placeholderKey));
}

4.2. Get position index of a rendering within a particular placeholder:

public static string GetComponentPositionOnPlaceHolder(System.Web.UI.Control webControl)
{
 if (webControl == null) return "0";
 var split = webControl.ID.Split('_');
 if (split.Length > 1)
 {
  int index = 0;
  if (int.TryParse(split.Last(), out index))
  {
   return (index + 1).ToString(CultureInfo.InvariantCulture);
  }
 }
 return "0";
}



5. Indexing


5.1. Simple Linq query

//Create a Search Context to the "index name" Index (could be SOLR/Lucene/something else)
using (var context = new ContentSearchManager.GetIndex("indexname").CreateSearchContext()) 
{
    //LINQ Query
    var query = context.GetQueryable.Where(i => i.Name.StartsWith("Something"));
}


5.2. Add single item to index

public static void AddItemToIndex(Item item, string indexName)
{
    var tempItem = (SitecoreIndexableItem)item;
    ContentSearchManager.GetIndex(indexName).Refresh(tempItem);
}
 
public static void UpdateItemInIndex(Item item, string indexName)
{
    var tempItem = (SitecoreIndexableItem)item;
    ContentSearchManager.GetIndex(indexName).Update(tempItem.UniqueId);
}
 
public static void DeleteItemInIndex(Item item, string indexName)
{
    var tempItem = (SitecoreIndexableItem)item;
    ContentSearchManager.GetIndex(indexName).Delete(tempItem.UniqueId);
 }


5.3. Queryable extensions

public static class QueryableExtensions
{
  public static IQueryable IsContentItem(this IQueryable query) where T : SearchResultItem
  {
    return query.IsDescendantOf(ItemIDs.ContentRoot);
  }
 
  public static IQueryable IsContextLanguage(this IQueryable query) where T : SearchResultItem
  {
    return query.Where(searchResultItem => searchResultItem.Language.Equals(Context.Language.Name));
  }
 
  public static IQueryable IsLatestVersion(this IQueryable query) where T : SearchResultItem
  {
    return query.Where(searchResultItem => searchResultItem["_latestversion"].Equals("1"));
  }
 
  public static IQueryable HasBaseTemplate(this IQueryable query, ID templateId) where T : SearchResultItem
  {
    var id = templateId.ToShortID().ToString().ToLowerInvariant();
    return query.Where(searchResultItem => searchResultItem["_basetemplates"].Contains(id));
  }
 
  public static IQueryable IsDescendantOf(this IQueryable query, ID parentId) where T : SearchResultItem
  {
    return query.Where(searchResultItem => searchResultItem.Paths.Any(ancestorId => ancestorId == parentId));
  }
}



6. DMS / xDB


6.1. DMS - hit the goal programmatically.

//Check that analytics are on...
if (Sitecore.Analytics.Tracker.IsActive &amp;&amp; Sitecore.Analytics.Tracker.CurrentPage != null)
{
  //Get the item goal.
  Item goalItem = Sitecore.Context.Database.GetItem(&amp;quot;{xxxxx-xxx-xxx-xxxxx}&amp;quot;);
  //Page event wrapper
  PageEventItem goal = new PageEventItem(goalItem);
  //Create the record that needs to  store the goal
   VisitorDataSet.PageEventsRow pageEventsRw = Sitecore.Analytics.Tracker.CurrentPage.Register(goal);
  //this is not mandatory
  pageEventsRw.Data = "custom text";
  Sitecore.Analytics.Tracker.Submit();
}


7. Other

7.1. Jumping between the sites of current Sitecore instance

Sitecore.Context.SetActiveSite(currentSiteName);