Search code examples
sitecoreglass-mapper

How to enable VersionCountDisabler for Glass Mapper in Sitecore for SitecoreQuery and SitecoreChildren attributes


The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work. In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception. SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor. To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.


Solution

  • This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery).

    New configuration:

    public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
    {
    }
    

    New attribute:

    public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
    {
        public SitecoreSharedQueryAttribute(string query) : base(query)
        {
        }
    
        public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
        {
            var config = new SitecoreSharedQueryConfiguration();
            this.Configure(propertyInfo, config);
            return config;
        }
    }
    

    New type mapper:

    public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
    {
        public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
            : base(parameters)
        {
        }
    
        public override object MapToProperty(AbstractDataMappingContext mappingContext)
        {
            var scConfig = Configuration as SitecoreQueryConfiguration;
            var scContext = mappingContext as SitecoreDataMappingContext;
    
            using (new VersionCountDisabler())
            {
                if (scConfig != null && scContext != null)
                {
                    string query = this.ParseQuery(scConfig.Query, scContext.Item);
    
                    if (scConfig.PropertyInfo.PropertyType.IsGenericType)
                    {
                        Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
    
                        if (typeof(IEnumerable<>) == outerType)
                        {
                            Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
    
                            Func<IEnumerable<Item>> getItems;
                            if (scConfig.IsRelative)
                            {
                                getItems = () =>
                                    {
                                        try
                                        {
                                            return scContext.Item.Axes.SelectItems(query);
                                        }
                                        catch (Exception ex)
                                        {
                                            throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
                                        }
                                    };
                            }
                            else
                            {
                                getItems = () =>
                                    {
                                        if (scConfig.UseQueryContext)
                                        {
                                            var conQuery = new Query(query);
                                            var queryContext = new QueryContext(scContext.Item.Database.DataManager);
    
                                            object obj = conQuery.Execute(queryContext);
                                            var contextArray = obj as QueryContext[];
                                            var context = obj as QueryContext;
    
                                            if (contextArray == null)
                                                contextArray = new[] { context };
    
                                            return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
                                        }
    
                                        return scContext.Item.Database.SelectItems(query);
                                    };
                            }
    
                            return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
                        }
    
                        throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
                    }
    
                    {
                        Item result;
                        if (scConfig.IsRelative)
                        {
                            result = scContext.Item.Axes.SelectSingleItem(query);
                        }
                        else
                        {
                            result = scContext.Item.Database.SelectSingleItem(query);
                        }
    
                        return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
                    }
                }
            }
    
            return null;
        }
    
        public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
        {
            return configuration is SitecoreSharedQueryConfiguration;
        }
    }
    

    And configure the new type mapper in your glass config (mapper and parameters for the constructor):

    container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
    container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
    container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
    container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
    container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
    container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
    

    You can then simply change the SitecoreQuery attribute on your model to SitecoreSharedQuery:

    [SitecoreSharedQuery("./*")]
    public virtual IEnumerable<YourModel> YourItems { get; set; }
    

    For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren query.

    Edit: Added bindings for IEnumerable<ISitecoreQueryParameter> as they are missing and therefor it threw an error.