Showing posts with label Glass. Show all posts
Showing posts with label Glass. Show all posts

Monday, 26 June 2017

Sitecore Glass Custom Field Mapping

Creating a custom Sitecore Glass field mapping

Recently I was building out some models using Sitecore Glass for a dynamic menu'ing module; when I realized that Glass didn't offer an out of the box solution to map an item's parent Id to a field.

There is however the SitecoreParent attribute, but this loads the entire object, which could compromise performance.  So I decided to see how easy it would be develop a custom mapping for Sitecore Glass.

I took a look at how the SitecoreIdAttribute and SitecoreParentAttribute worked and set about creating my own SitecoreParentIdAttribute.

There are three pieces that you need when creating a Glass mapper:
  1. The attribute
  2. The configuration
  3. The data mapper

The Attribute

    public class SitecoreParentIdAttribute : AbstractPropertyAttribute
    {

        protected IEnumerable<type> AcceptedTypes { get; private set; }
        public SitecoreParentIdAttribute()

        {
            AcceptedTypes = new Type[2]
            {
                typeof (ID),
                typeof (Guid)
            };
        }

        public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
        {
            var config = new ParentIdConfiguration();
            Configure(propertyInfo, config);
            return config;
        }

        public void Configure(PropertyInfo propertyInfo, ParentIdConfiguration config)
        {
            if (AcceptedTypes.All(x => propertyInfo.PropertyType != x))
                throw new ConfigurationException(string.Format("Property type {0} not supported as an ID on {1}", propertyInfo.PropertyType.FullName, propertyInfo.DeclaringType.FullName));
            config.Type = propertyInfo.PropertyType;
            base.Configure(propertyInfo, config);
        }
    }

In the above code we define the attribute, the accepted types (Guid and ID), and the configuration class that will hold any additional information.


The Configuration

    public class ParentIdConfiguration : AbstractPropertyConfiguration
    {
        public Type Type { get; set; }

        protected override AbstractPropertyConfiguration CreateCopy()
        {
            return new ParentIdConfiguration();
        }

        protected override void Copy(AbstractPropertyConfiguration copy)
        {
            (copy as ParentIdConfiguration).Type = Type;
            base.Copy(copy);
        }
    }

In the above code we define the configuration class that will also be used by the mapper to tie it back to the attribute.


The Data Mapper

   public class SitecoreParentIdMapper : AbstractDataMapper
    {
        private Func<Item, object> _getValue;

        public SitecoreParentIdMapper()
        {
            ReadOnly = true;
        }

        public override void MapToCms(AbstractDataMappingContext mappingContext)
        {
            throw new NotImplementedException();
        }

        public override object MapToProperty(AbstractDataMappingContext mappingContext)
        {
            return _getValue(((SitecoreDataMappingContext) mappingContext).Item);
        }

        public override void Setup(DataMapperResolverArgs args)
        {
            if (args.PropertyConfiguration.PropertyInfo.PropertyType == typeof (Guid))
                _getValue = item => item.ParentID.Guid;
            else if (args.PropertyConfiguration.PropertyInfo.PropertyType == typeof (ID))
                _getValue = item => item.ParentID;
            else
                throw new NotSupportedException(
                    "The type {0} on {0}.{1} is not supported by SitecoreIdMapper".Formatted(
                        args.PropertyConfiguration.PropertyInfo.ReflectedType.FullName,
                        args.PropertyConfiguration.PropertyInfo.Name));
            base.Setup(args);
        }

        public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
        {
            return configuration is ParentIdConfiguration;
        }
    }


In the above code we define how to map to the field (current item being the context).  The most important pieces here are the:
  • Setup method - where we define the mapping
  • CanHandle method - where we define which attributes (via configuration) are tied to the mapping.  
We don't need to implement the MapToCMS method as we will never want to update Sitecore with this value.  So to ensure that the mapper knows  not to use the method; we set the ReadOnly boolean flag to true.

N.B. If you break point the CanHandle method you will see it hit multiple times as each field with a Glass attribute on it is parsed.

Here it is in use:
[SitecoreParentId]
public virtual Guid ParentId { get; set; }
You'll need to register your mapping with whatever IoC framework, you are using. In a future post I will go into this in more details, but suffice to say for Sitecore's own IoC framework I did:
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddGlassMaps(this IServiceCollection serviceCollection, string assemblyDefinition)
{
    Directory.SetCurrentDirectory(AssemblyDirectory);
    var config = new Config();

    var resolver = new DependencyResolver(config);

    var context = Context.Create(resolver);
    resolver.DataMapperFactory.Add(() => new SitecoreParentIdMapper());

    context.Load(GlassLoaders(assemblyDefinition));
}
And thats all there is to it :)

Friday, 16 October 2015

Sitecore Glass doesn't work with MediaRequestProtection

There's a new feature in Sitecore 7.5, called Media Request Protection.  This is where Sitecore adds a hash to the media URL, to stop it being tampered with.  You can manage it in the Sitecore.Media.RequestProtection.config file.  

In fact if you need to add to the parameters in the Media URL, perhaps for a custom image handler; you will need to ensure that your new parameters are registered in this configuration file, otherwise they won't come through to your handler.
 
We just went live with new Sitecore 8.0 site and noticed lots of errors in the log something like: 

948 20:44:25 ERROR MediaRequestProtection: An invalid/missing hash value was encountered. The expected hash value: E43FE65CB6A819B4976A19BEFF730F204C7386DB. Media URL: /~/media/Images/Heroes/card-bikes.ashx?h=279&w=576, Referring URL: 7084 20:44:25 ERROR MediaRequestProtection: An invalid/missing hash value was encountered. The expected hash value: 098D0163B651C8F5944A0183BB29012295FA8B54. Media URL: /~/media/Images/Heroes/product-card.ashx?h=279&w=279, Referring URL: 

The errors seemed to be so frequent that it was causing memory to spike, which was impacting the site.

Also (and more importantly) what this meant was that Sitecore wasn't resizing/caching any of the images before sending them to the client.  Sitecore's own image handlers were not working.

On investigation we realized that Sitecore Glass's Mvc Helper methods like @Editable and @RenderImage were not rendering the new Media Request security hash. Doing a little digging I found: https://github.com/mikeedwards83/Glass.Mapper/issues/93 The article mentions that it is a known issue with Sitecore Glass and is fixed in Sitecore Glass version 4.0.

The options we came up with were 
  • Turn it off
  • Upgrade Sitecore Glass
  • Write our own helper method or extend the Sitecore Glass ones
In the end we opted to upgrade.

Thursday, 15 October 2015

Extending TDS glass entity generation

One of the really cool features of Hedeghog's TDS (Team Developer for Sitecore - https://www.hhogdev.com/products/team-development-for-sitecore/overview.aspx) is that it can create Sitecore Glass entities automatically, when you synchronize specific portions of the Sitecore tree.  

One of the issues I came across was that when it creates properties for Sitecore link and list fields like DropLink and TreeListEx; it uses GUIDs rather than the entity in question.  So how do you get the code generation templates to link to the entity rather than to a GUID?

There is a really simple fix that involves 4 steps.


Let's assume that you have an entity called Product Filter which has a TreeListEx field, which references a list of Brand items.
  1. In the Sitecore core database navigate to the Field Types folder under system and create a copy of the TreeListEx field naming it something like Brand TreeListEx

    N.B. I also create a new folder to hold all my Entity Types so that they are easy to find when editing a template:


  2. In the template that uses the field; change the type to be Brand TreeListEx (or whatever you called it).

  3. In Visual Studio navigate to the TDS project that contains the code generation templates. 

    N.B. This is probably your Sitecore templates project. 

    Open up the code generation template for the glass items - in my code this was called glassv3item.tt.  Scroll down the script until you find the following method:

    public static string GetGlassFieldType(SitecoreField field)
    
    Locate the switch statement add the following on a new line:
    case "brand treelistex":
         return "IEnumerable<Brand>";
    N.B. The text in the case statement must be lowercase and must exactly match the name of the newly created field in the core database.

    N.B Always use IEnumerable for collections.

    N.B. For a single item reference field like a DropLink; change the return part to be something like:

    return "Brand";
  4. Now synchronize the Sitecore items project so that the Sitecore Glass code generation is redone, and you should then be good to go.