Wednesday, May 29, 2013

Programmatically updating Rendering datasource

I've recently discovered an undocumented feature of Sitecore and decided to share my experience.

The task at hand was to import data from a thrid party system into Sitecore generating items based on specific template. In the standard values of that template I had a few MVC renderings (views) assigned. A lot of those renderings had to have datasource set to be pointing either to the newly created item for imported record or to a child of that item. Updating datasource for all assigned in SV renderings turned out to be not so straight forward task. The first thing I attempted is to update Datasource property on each rendering, which turned out to be a wrong decision. Updating this property makes Layout Details windows to display duplicate renderings, even though there are none. You don't have duplicates, but each rendering has two different values if you look at the raw value of the layout field. Needless to say that rendering engine had issues with processing this value as well. What I ended up doing is not updating Datasource property, but one of DynamicProperties called "s:ds". Surprisingly, when you update it's value, system also updates Datasource property and everything starts magically working. Here is what I ended up doing:

LayoutDefinition layout = LayoutDefinition.Parse(bioItem[Sitecore.FieldIDs.LayoutField]);

foreach (DeviceDefinition device in layout.Devices) {
   if (device.Renderings != null) { 
      for(var i =0; i < device.Renderings.Count;i++) { 
         RenderingDefinition rendering = (RenderingDefinition)device.Renderings[i];     
         if (rendering.ItemID == renderingId) { 
            //rendering.Datasource = renderingDatasource;  // creates duplicates rendering in the layout details window.  
            rendering.DynamicProperties.Where(p => p.Name == "s:ds").ToList().ForEach(p => p.Value = datasource); 
         } 
      } 
   } 

Wednesday, May 22, 2013

Sitecore with MVC 4

Task at hand:

Sitecore works pretty nicely with MVC 4 until you try to use default MVC functionality like bundling. When you attempt to use it and run the solution in release mode, you'll see all your bundles returning 404. The reason for that is Mvc.IllegalRoutes setting in Sitecore.Mvc.config. John West has written as series of post regarding this functionality, but I figured I would post my solution.

Solution:

  • Remove {controller}/{action}/{id} from Mvc.IllegalRoutes:
    <setting name="Mvc.IllegalRoutes" value="" />
  • Create a new class for a route contraint like so: 
public class SitecoreConstraint : IRouteConstraint
    {
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (Sitecore.Context.Item != null && Sitecore.Context.Item.Visualization.Layout != null)
            {
                return false;                
            }
            return true;
        }
    }
  • Add newly created constraint to RouteConfig:
public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new { sitecoreRoute = new SitecoreConstraint() }
            );
        }
    }

That should solve the routing problem and allow you to use bundles with Sitecore.

UPDATE:

I just upgraded our solution from 7.0 to 7.2 (Update-2) and bundles seem to be working without any issue when I add "/bundles" to "IgnoreUrlPrefixes". No modification of Mvc.IllegalRoutes was required.

One more thing I had to change to make css @import statement work.

Instead of:

bundles.Add(new StyleBundle("~/bundles/css").Include("~/assets/design_new/css/main.css""~/assets/design_new/css/all.css"));


I had to place all css files into the same directory and use CssRewriteUrlTransform object:

bundles.Add(new StyleBundle("~/bundles/css").Include("~/assets/design_new/css/*.css"new CssRewriteUrlTransform()));

Wednesday, May 8, 2013

Sitecore 6.6 MVC EditFrame Helper

If you ever attempted using EditFrame with MVC, you probably had to write a helper to render it out. I figured I would share an EditFrame helper code I came up with. I'm using MVC4 with Sitecore 6.6. Edit Frame seems to be rendering the same way as it does with ASP.NET Forms.

C# Helper code:


public static class EditorFrameHelper
    {
        public static EditFrame EditFrameControl;

        private class FrameEditor : IDisposable
        {
            private bool disposed;
            private HtmlHelper html;

            public FrameEditor(HtmlHelper html, string dataSource = null, string buttons = null)
            {
                this.html = html;
                EditorFrameHelper.EditFrameControl = new EditFrame
                {
                    DataSource = dataSource ?? "/sitecore/content/home",
                    Buttons = buttons ?? "/sitecore/content/Applications/WebEdit/Edit Frame Buttons/Default"
                };
                HtmlTextWriter output = new HtmlTextWriter(html.ViewContext.Writer);
                EditorFrameHelper.EditFrameControl.RenderFirstPart(output);
            }

            public void Dispose()
            {
                if (disposed) return;

                disposed = true;

                EditorFrameHelper.EditFrameControl.RenderLastPart(new HtmlTextWriter(html.ViewContext.Writer));
                EditorFrameHelper.EditFrameControl.Dispose();
            }
        }

        public static IDisposable EditFrame(this HtmlHelper html, string dataSource = null, string buttons = null)
        {
            return new FrameEditor(html, dataSource, buttons);
        }
    }

View code:

@using (Html.EditFrame(Model.Key.SitecoreItem.Paths.FullPath,"/sitecore/content/Applications/WebEdit/Edit Frame Buttons/RelatedContentSection"))
{
    <h4 id="@Model.Key.SitecoreItem.Name.HtmlAnchor()">@Model.Key.PanelType.Value</h4>
}