Skip to main content

Sitecore

Sitecore Upgrades: A Mini-Series Part 4 ORM and Other Dependency Updates

agile backlog groom

For those of you upgrading a Sitecore instance for the first time, welcome to the hardest part of the upgrade. Upgrading your code base to continue functioning the exact same as it did prior to the upgrade can prove to be quite difficult. The newer versions of your dependencies could remove classes and functions that you used heavily throughout the solution. When trying to estimate how long it will take to upgrade your code base, you will need to take into account the current versions of all your dependencies and determine what versions of those dependencies you will need to upgrade to. This blog post will provide tips on upgrading your code base and how to address the elephant in the room, ORMs (Object Relational Mappers).

Updating Sitecore Dependencies

The first thing you will need to do is update the Target Framework of all of the projects in your solution. To do this in Visual Studio, simply right-click a project and open up its Properties. Then update your project’s Target Framework setting to match the .NET version required by your new Sitecore version. The menu will look like the below screenshot:

Targetframework

After updating this setting, unload and reload each project back into the solution. Then navigate to the NuGet Package Manager for the Solution. Connect to the Sitecore NuGet feed. Uninstall the Sitecore NuGet packages. Then install the same Sitecore NuGet packages using the version that is compatible with the new version of Sitecore you are upgrading to. Install these packages with the Dependency Behavior set to Lowest.

Sitecorenuget

WARNING: Do not just install the latest version of the NuGet package and go about your day. The latest version of the package will correspond with the latest Sitecore release. You may not be upgrading to the latest version of Sitecore.

After upgrading the NuGet references, you will then need to peruse through your code in each of your projects. You will need to make sure your Sitecore API calls and implementations are still supported by the newer version of Sitecore. If not, you will need to adjust your code accordingly in order for your site to function like it did on the older version. I also would take the time to make sure if any of the projects have references to the older version of Sitecore. This usually happens if the Sitecore references were added without using NuGet. You will want to take care to install the Sitecore references to projects like these via NuGet Package Manager.

Updating Non-Sitecore Dependencies

Follow the same steps described above for upgrading your non-Sitecore dependencies. First navigate to the NuGet Package Manager and be sure to install the same upgraded versions across the projects as needed. Then check to make sure all projects did not have any of these dependencies installed without NuGet. Update accordingly.

Some of your dependencies may not have a NuGet package version that is compatible with your new version of Sitecore. So what then? You have a few options. You can wait to do your upgrade process until the NuGet package is available. This may never come if that dependency is no longer being maintained. Even if it is being maintained, it may not be available in the time you are given to complete the upgrade. This leaves you with the other two options. You have the option to download the current version of the dependency and make it compatible with your new version of Sitecore. The final (nuclear) option you have is removing that dependency and implementing your own code to perform its functionality. I do not recommend the final option unless absolutely necessary. Updating the dependency yourself will likely be the path you have to take.

What is an ORM?

ORM stands for Object Relational Mapper. In Sitecore, the most popular ORMs are Glass Mapper and Synthesis. These tools allow you to map content-authored data on an item to a strongly-typed C# object easily. They are impressively built libraries that are easy to use. For example, I can construct an object like the below with Glass Mapper:

[SitecoreType(TemplateID = MySampleGlassItemId, AutoMap = true)]
public class MySampleGlassObject : ItemBase 
{
     public virtual string Title { get; set; }
     public virtual string Text { get; set; }
     public virtual string Description { get; set; }
}

I can then retrieve my Sitecore item with those three properties with one simple call that will cast it to the above object.

MySampleGlassObject myObject = _mvcContext.GetDatasourceItem<MySampleGlassObject>();

With property decorators such as [SitecoreChildren], you can even retrieve tons of items that are auto-mapped to C# objects all without a developer having to retrieve each individual field.

To Keep ORM or Not To Keep ORM

When upgrading your solution that makes use of an ORM, you will have two paths forward: get rid of using the ORM or upgrade the ORM. In an ideal world, it would be a no brainer to want to upgrade the ORM to keep all of your implementations in tact with little updating. Unfortunately, we are not in an ideal world. The newer version of the ORM may remove methods it previously supported. Your code base may require some serious updating to work around these now obsolete functions.

Upgrading your ORM can be an extremely difficult task. For example, the changes between Glass Mapper 4 and Glass Mapper 5 are significant. The steps you would need to take are well documented in this blog here. The GlassController no longer exists in Glass Mapper 5. If all of your controllers are dependent upon using this controller (or you even have other dependencies built upon using this controller), you will need to find a work around. I have written a replacement controller that you can use to replace the now non-existent GlassController. If you do not use it, you will need to write your own work around similar to the linked blog post. Other changes such as the removal of the IsLazy decorator could call for sweeping changes throughout your code especially for components that required auto-mapping tons of child items.

The point I am trying to make here is that upgrading an ORM may not even be worth your time. Even after getting your code to successfully compile using the upgraded ORM, you will then have to heavily test your sites ensuring the site’s behavior is still the same as before. Troubleshooting and diagnosing the issues attributed to your ORM is quite difficult.

This headache can be avoided by removing your solution’s dependency on an ORM. This may seem like a tall order. After all, it would require updating every component to get rid of its reliance on the ORM. However, I would argue that once you get the development pattern down, it becomes very easy, mindless work to plow through. Doing so will make future upgrades even easier because you will have one less dependency to update. It also prevents having to wade through obscure ORM configuration issues. If the ORM upgrade is going to require a lot of code rewrite, why not simply rewrite the ORM out completely? I am going to take it a step further here and recommend to architect all future solutions without using an ORM to make upgrades as painless as possible.

What will your code look like going ORM-less?

Your code base will contain a lot more Sitecore API calls. Let’s circle back to the Glass Mapper code sample I used to retrieve an object with Title, Text, and Description properties. To retrieve those property values without Glass Mapper, it would look like the below:

var header = myItem.Fields["Header"].Value;
var text = myItem.Fields["Text"].Value;
var description = myItem.Fields["Description"].Value;

Very simple right? I think so. It may seem like it will be tough to replace ORM’s helper methods in the view to make fields editable. It’s really not difficult to switch over to the Sitecore API to keep these fields editable. I recommend employing custom edit frames, custom experience buttons, or Sitecore’s HTML helpers.

Keeping Glass Mapper and Replacing GlassController

If you are choosing to keep Glass Mapper as a dependency and you need to replace the GlassController, the below code will do the trick!

using Glass.Mapper;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Web.Mvc;
using Sitecore.Data.Items;
using Sitecore.Mvc.Controllers;
using System;
using System.Diagnostics.CodeAnalysis;

namespace MyBlogNamespace
{
    public class ReplacementGlassController : SitecoreController
    {
        public IMvcContext MvcContext { get; set; }
        public ISitecoreService SitecoreService { get; set; }

        public IGlassHtml GlassHtml { get; set; }

        public IRenderingContext RenderingContextWrapper { get; set; }      

        [ExcludeFromCodeCoverage]
        public ReplacementGlassController(IMvcContext mvcContext, ISitecoreService sitecoreService)
          : this(mvcContext, sitecoreService, new RenderingContextMvcWrapper())
        {
            
        }       

        public ReplacementGlassController(
          IMvcContext mvcContext,
          ISitecoreService sitecoreService,
          IRenderingContext renderingContextWrapper)
        {
            MvcContext = mvcContext;
            GlassHtml = mvcContext?.GlassHtml;
            SitecoreService = sitecoreService;
            RenderingContextWrapper = renderingContextWrapper;
        }

        /// <summary>
        /// Returns either the item specified by the DataSource or the current context item
        /// </summary>
        /// <value>The layout item.</value>
        [ExcludeFromCodeCoverage]
        public virtual Item LayoutItem
        {
            get
            {
                return DataSourceItem ?? ContextItem;
            }
        }

        /// <summary>
        /// Returns either the item specified by the current context item
        /// </summary>
        /// <value>The layout item.</value>
        [ExcludeFromCodeCoverage]
        public virtual Item ContextItem
        {
            get
            {
                return Sitecore.Context.Item;
            }
        }

        /// <summary>
        /// Returns the item specificed by the data source only. Returns null if no datasource set
        /// </summary>
        [ExcludeFromCodeCoverage]
        public virtual Item DataSourceItem
        {
            get
            {
                if (!RenderingContextWrapper.HasDataSource)
                    return null;
                return Sitecore.Context.Database.GetItem(RenderingContextWrapper.GetDataSource());
            }
        }

        /// <summary>Returns the Context Item as strongly typed</summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected T GetContextItem<T>(bool isLazy = false, bool inferType = false) where T : class
        {
            return MvcContext.GetContextItem<T>(new GetKnownOptions()
            {
                InferType = inferType,
                Lazy = isLazy ? LazyLoading.Enabled : LazyLoading.Disabled
            });
        }

        /// <summary>Returns the Data Source Item as strongly typed</summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected T GetDataSourceItem<T>(bool isLazy = false, bool inferType = false) where T : class
        {
            if (!RenderingContextWrapper.HasDataSource)
                return default(T);
            string dataSource = RenderingContextWrapper.GetDataSource();
            if (string.IsNullOrEmpty(dataSource))
                return default(T);
            return SitecoreService.GetItem<T>(dataSource, options => new GetKnownOptions()
            {
                InferType = inferType,
                Lazy = isLazy ? LazyLoading.Enabled : LazyLoading.Disabled
            });
        }

        /// <summary>Returns the Layout Item as strongly typed</summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected T GetLayoutItem<T>(bool isLazy = false, bool inferType = false) where T : class
        {
            if (!RenderingContextWrapper.HasDataSource)
                return GetContextItem<T>(isLazy, inferType);
            return GetDataSourceItem<T>(isLazy, inferType);
        }

        protected virtual T GetRenderingParameters<T>() where T : class
        {
            string renderingParameters = RenderingContextWrapper.GetRenderingParameters();
            if (!renderingParameters.HasValue())
                return default(T);
            return GlassHtml.GetRenderingParameters<T>(renderingParameters);
        }

        /// <summary>Returns the data source item.</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="isLazy"></param>
        /// <param name="inferType"></param>
        /// <returns></returns>
        [Obsolete("Use GetDataSourceItem")]
        protected virtual T GetRenderingItem<T>(bool isLazy = false, bool inferType = false) where T : class
        {
            return GetDataSourceItem<T>(isLazy, inferType);
        }

        /// <summary>
        /// if the rendering context and data source has been set then returns the data source item, otherwise returns the context item.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="isLazy"></param>
        /// <param name="inferType"></param>
        /// <returns></returns>
        [Obsolete("Use GetLayoutItem")]
        protected virtual T GetControllerItem<T>(bool isLazy = false, bool inferType = false) where T : class
        {
            return GetLayoutItem<T>(isLazy, inferType);
        }           
    }
}

You will then need to go through all of your controllers that are reliant on the GlassController and update their constructors to receive a dependency injected IMvcContext and ISitecoreService. After this, you will need to make all of your code function without the now obsolete Glass functions. Don’t know how to implement dependency injection? Stay tuned for my next blog to explain how to do this!

Closing Remarks

This portion of the upgrade takes time. You will run into issues where certain dependencies are not deployed or are not being referenced properly. You will run into tons of Yellow Screens of Death. Be patient. Try to keep a clear head and take it one step at a time. Successfully getting a solution upgraded is a good feeling and a well-earned achievement.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Zach Gay, Senior Technical Consultant, Perficient's Sitecore practice

More from this Author

Categories
Follow Us
TwitterLinkedinFacebookYoutubeInstagram