How to Set a Rendering Datasource Programmatically in Sitecore 8+

Code examples follow

As most are probably aware, Sitecore has given us a new way to manage presentation in that they introduced the concept of "Shared Layout" and "Final Layout".  The purpose of this post is not to understand the new functionality, but to illustrate how to access the new features in code.

Since most of the behavior can be done using Sitecore utility classes, our best option here is to create an ItemExtension. The example controller which calls this code doesn't really do anything.  This is just meant to show the quickest way to get a uniqueId for a given rendering.  

Also please note that the DeviceDefinition class contains methods to get a rendering based off its item id. But because complex solutions could have multiple instances of the same rendering, we find that this method (based off the unique id) gives more predictable results.

Here is the code for the extension class, and an example controller.

ItemExtension:

using System;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Events;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.ExperienceEditor.Utils;
using Sitecore.Layouts;

namespace MyFoundation.Sc.Extensions
{
    public static class ItemExtensions
    {
        public static void SetRenderingDatasource(this Item item, Guid uniqueId, string dataSource, bool setSharedLayout = false)
        {
            // First get the shared and final layout from the item
            // We'll need the shared layout later to save, so go ahead and get it now
            LayoutDefinition sharedLayout = GetSharedLayout(item);
            LayoutDefinition finalLayout = GetFinalLayout(item);

            // Always set the final layout.
            SetRenderingDatasource(uniqueId, dataSource, finalLayout);
            // If the context is in the experience editor, and shared layout is checked
            // Or if the boolean override setSharedLayout has been set to true
            if (WebUtility.IsEditAllVersionsTicked() || setSharedLayout)
            {
                // Set the shared layout
                SetRenderingDatasource(uniqueId, dataSource, sharedLayout);
            }

            // Set the altered layout definitions back to the item
            // This utility function also executes the save action
            ItemUtil.SetLayoutDetails(item, sharedLayout.ToXml(), finalLayout.ToXml());
        }

        private static void SetRenderingDatasource(Guid uniqueId, string dataSource, LayoutDefinition sharedLayout)
        {
            // get the context device
            DeviceDefinition currentDevice = sharedLayout.GetDevice(Context.Device.ID.ToString());
            // get the rendering by unique ID. 
            // *We've had trouble getting Guid.ToString() to produce a match 
            // due to normalization, hence ID.Parse(...)
            RenderingDefinition rendering = currentDevice.GetRenderingByUniqueId(ID.Parse(uniqueId).ToString());
            if (rendering == null)
                return;
            // if we have a rendering, set the datasource string 
            // (should be an ID, not a path)
            rendering.Datasource = dataSource;
        }

        private static LayoutDefinition GetFinalLayout(Item item)
            => GetLayoutDefinition(item, FieldIDs.FinalLayoutField);

        private static LayoutDefinition GetSharedLayout(Item item)
            => GetLayoutDefinition(item, FieldIDs.LayoutField);

        private static LayoutDefinition GetLayoutDefinition(Item item, ID fieldId)
            => LayoutDefinition.Parse(LayoutField.GetFieldValue(item.Fields[fieldId]));
    }
} 

Controller:

using System.Web.Mvc;
using MyFoundation.Sc.Extensions;
using Sitecore.Data.Items;
using Sitecore.Mvc.Controllers;
using Sitecore.Mvc.Presentation;

namespace MyProject.Website.Controllers
{
    public class MyController : SitecoreController
    {
        public ContentResult MyAction(string dataSourceId)
        {
            RenderingContext renderingContext = RenderingContext.CurrentOrNull;
            Rendering currentRendering = renderingContext.Rendering;
            Item currentItem = renderingContext.ContextItem;

            currentItem.SetRenderingDatasource(currentRendering.UniqueId, dataSourceId);

            return new ContentResult { Content = dataSourceId }; 
        }
    }
}