November Happy Hour will be moved to Thursday December 5th.

Custom ContentScannerExtension to update property order, order not changed

Vote:
 

I've made a custom ContentScannerExtension that sets Order on the PropertyDefinitionModel in AssignValuesToProperties() method. I can see that i runs but the properties aren't sorted.
What could be wrong?

The scanner extensions looks like this and the log shows that properties gets the order i expect.

    [ServiceConfiguration(typeof(ContentScannerExtension))]
    public class SortOrderScannerExtension : ContentScannerExtension
    {
        private ILogger _logger = LogManager.GetLogger(typeof(SortOrderScannerExtension));
        public override void AssignValuesToProperties(ContentTypeModel contentTypeModel)
        {
            _logger.Information("Handling {0}", contentTypeModel.ModelType.FullName);
            base.AssignValuesToProperties(contentTypeModel);
            var propertySortOrderType = contentTypeModel.ModelType.GetNestedTypes(BindingFlags.Public|BindingFlags.NonPublic).FirstOrDefault(x => typeof(IPropertySortOrder).IsAssignableFrom(x));
            _logger.Information("propertySortOrder: {0}", propertySortOrderType?.FullName);
            if (propertySortOrderType == null)
                return;
            var sortOrders = (IPropertySortOrder)Activator.CreateInstance(propertySortOrderType);
            foreach (var propDef in contentTypeModel.PropertyDefinitionModels)
            {
                var sortOrder = sortOrders.GetSortOrder(propDef.Name);
                if (sortOrder > -1)
                {
                    _logger.Information("- property {0}'s sortOrder = {1}", propDef.Name, sortOrder);
                    propDef.Order = sortOrder;
                }
            }
        }
    }
#157837
Sep 22, 2016 18:41
Vote:
 

Haven't done this myself, but isn't it so that you might need to save modified property definition?

#157842
Sep 22, 2016 23:39
Vote:
 

A ContentScannerExtensions is a part of episervers modeltype discovery and syncronization process, so i should not have to save anything. I think :)

From API docs for AssignValuesToProperties(): "Override to modify the properties model done by default assigner."

The default assigner will for example set Order from the DisplayAttribute... 

#157844
Sep 23, 2016 7:08
Vote:
 

Hm, changing display works.

#157846
Sep 23, 2016 8:12
Vote:
 

Aaaaaaaaaaaaah, is it that you cant change sortorder from code? Maybe i need to make an initialization module that runs after ModelSyncInitialization!

"So as of EPiServer 7 we have changed this behavior, sort index is never enforced when saving properties but always enforced (1..n) if you re-order using drag-n-drop or use the move up and down arrows in admin mode. To make sure the sort order is consistent we order first by index and for properties that do not have a sort index we “put them at the end” and order by the ID, so order of creation basically."

And then in the sync code:

	if (!propertyDefinition.NullableFieldOrder.HasValue && model.Order.HasValue)
	{
		propertyDefinition.FieldOrder = model.Order.Value;
	}

which looks like it will only update fieldorder if it's not having a value already!

#157847
Sep 23, 2016 8:21
Vote:
 

I solved it like this:

    public class ContentTypeSyncronizationInitialization : IInitializableModule
    {
        private ILogger _logger = LogManager.GetLogger(typeof(ContentTypeSyncronizationInitialization));
        private IContentTypeRepository _contentTypeDefinitionRepository;
        private IPropertyDefinitionRepository _propertyTypeDefinitionRepository;
        public void Initialize(InitializationEngine context)
        {
            _contentTypeDefinitionRepository = context.Locate.Advanced.GetInstance<IContentTypeRepository>();
            _propertyTypeDefinitionRepository = context.Locate.Advanced.GetInstance<IPropertyDefinitionRepository>();
            var contentDefinitionTypes = _contentTypeDefinitionRepository.List();
            var contentDataType = typeof(EPiServer.Core.IContentData);
            var contentTypes = typeof(ContentTypeSyncronizationInitialization).Assembly
                                .GetTypes().Where(x => contentDataType.IsAssignableFrom(x));
            foreach (var contentType in contentTypes)
            {
                var contentTypeDefinition = contentDefinitionTypes.SingleOrDefault(x => x.ModelType == contentType);
                if (contentTypeDefinition == null)
                    continue;
                _logger.Information("Updating content type {0} ({1})", contentTypeDefinition.DisplayName, contentTypeDefinition.Name);
                UpdatePropertyDefinitionsForContentTypeDefinition(contentType, contentTypeDefinition);
            }
        }

        private void UpdatePropertyDefinitionsForContentTypeDefinition(Type contentType, ContentType contentTypeDefinition)
        {
            var propertyDefinitions = _propertyTypeDefinitionRepository.List(contentTypeDefinition.ID);
            var propertySortOrderType = contentType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(x => typeof(IPropertySortOrder).IsAssignableFrom(x));
            if (propertySortOrderType == null)
                return;
            var sortOrders = (IPropertySortOrder)Activator.CreateInstance(propertySortOrderType);
            foreach (var propDef in propertyDefinitions)
            {
                var sortOrder = sortOrders.GetSortOrder(propDef.Name);
                if (sortOrder > -1 && sortOrder != propDef.FieldOrder)
                {
                    _logger.Information("- Updating property {0}'s sort order to {1}", propDef.Name, sortOrder);
                    var propDefWritable = propDef.CreateWritableClone();
                    propDefWritable.FieldOrder = sortOrder;
                    _propertyTypeDefinitionRepository.Save(propDefWritable);
                }
            }
        }
        public void Uninitialize(InitializationEngine context)
        {
            
        }
    }

To sort my properties i just add a nested class to my pagetype like this:

public class MyPage : PageBase
{
  protected class PropertySortOrder : PropertySortOrder<MyPage>
  {
    public PropertySortOrder()
    {
      Add(x => x.Heading);
      Add(x => x.MainBody);
    }
  }
  public virtual XhtmlString MainBody {get;set;}
  public virtual string Heading {get;set;}
}

Properties will be sorted in the order they are added.

IPropertySortOrder and PropertySortOrder<>:

    public interface IPropertySortOrder
    {
        int GetSortOrder(string propertyName);
    }
    public abstract class PropertySortOrder<TContentType> : IPropertySortOrder
    {
        private Dictionary<string, int> _sortOrders = new Dictionary<string, int>();
        private int _order = 1;
        protected void Add<TReturnType>(Expression<Func<TContentType, TReturnType>> expr)
        {
            var memberExpr = expr.Body as MemberExpression;
            if (memberExpr == null)
                return;
            var propName = memberExpr.Member.Name;
            _sortOrders[propName] = _order++;
        }

        public int GetSortOrder(string propertyName)
        {
            int sortOrder = -1;
            return _sortOrders.TryGetValue(propertyName, out sortOrder) ? sortOrder : -1;
        }
    }
#157851
Sep 23, 2016 9:30
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.