Strongly typed categories

  • Mar 28, 2016
  • EPiServer
  • |

EPiServer uses a strongly typed principle to create entries in the database from code. This gives benefits for your application like good architecture, better maintainability and simple deployments. Different parts within EPiServer uses this principle, such as pages, blocks and the new forms module.

 

How does it work?

 

Internally EPiServer uses initialization modules for setting configurations while starting up the application. On of that module is scanning types in all available assemblies. EPiServer uses reflection for this. One of the great benefits from EPiServer is that a lot of functionality that EPiServer uses internally is also available in the public API.

In the implementation of the interface ITypeScannerLookup, all registered types by EPiServer are stored. This type contains a list of all content types, initialization module types, form types and much more. Types are searched with reflection. This functionality is also available for in your own project. If you would like your own types registered in the ITypeScannerLookup, the TypeScannerRegistration attribute can be used.  This attribute is placed in the AssemblyInfo class. Below an example of the attribute. With this configuration, I tell EPiServer to search for Category types or for types that inherit from Category and store them in ITypeScannerLookup.

 

[assembly: TypeScannerRegistration(typeof(Category))]

Categories in EPiServer can be created in the admin mode. Categories can also be created from code, there are some examples available online how you can do that. With this blog, I will create categories from code and I will use the same way as how EPiServer create content types from code. So we could call this ‘strongly typed categories’. 

[CategoryType(
            Guid = "4ECF086E-5BC1-4C8A-8AB1-1761DE8C26D9", 
            Name = "Product type1", 
            Description = "Product type")]
        public class ProductTypeCategory : Category
        {
     
        }
     
        [CategoryType(
            Guid = "CB401650-E22B-415E-88DC-5EBE073B7432",
            Name = "Jeans",
            Description = "Jeans",
            Parent = typeof(ProductTypeCategory))]
        public class JeansCategory : Category
        {
             
        }
     
        [CategoryType(
            Guid = "68DB3E60-880A-491E-9155-1F7954222D34",
            Name = "Shirts",
            Description = "Shirts",
            Parent = typeof(ProductTypeCategory))]
        public class ShirtsCategory : Category
        {
     
        }
     
        [CategoryType(
            Guid = "60810B30-025F-466D-96B2-EB294777D959",
            Name = "Shirts short sleeve",
            Description = "Shirts short sleeve",
            Parent = typeof(ShirtsCategory))]
        public class ShirtsShortSleeveCategory : Category
        {
     
        }
     
        [CategoryType(
            Guid = "8BE625D5-F703-463B-996C-29DB94598945",
            Name = "Shirts long sleeve",
            Description = "Shirts long sleeve",
            Parent = typeof(ShirtsCategory))]
        public class ShirtsLongSleeveCategory : Category
        {
     
        }

In the code snippet, I have created multiple category classes. Each class inherits from the EPiServer type, Category. The new CategoryType class attribute is used to define information about the category. I also added a Parent property because a category can hold child categories. The CategoryType is a simple attribute class.

[AttributeUsage(AttributeTargets.Class)]
        public class CategoryTypeAttribute : Attribute
        {
            public string Guid { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public Type Parent { get; set; }
        }

Next the initialization module. On application startup, I want to create the categories in the database. Because I’ve used the TypeScannerRegistration attribute in the AssemblyInfo my category classes are registered in the ITypeScannerLookup. I’ve created the CategoryLookup class that is called by my initialization module to receive all registered category classes and save them in EPiServer.

public class CategoryLookup
        {
            private readonly CategoryRepository _categoryRepository;
            private readonly ITypeScannerLookup _typeScannerLookup;
     
            private readonly IList<Category> _allCategories;
            private readonly Category _rootCategory;
     
            public CategoryLookup()
            {
                _categoryRepository = ServiceLocator.Current.GetInstance<CategoryRepository>();
                _typeScannerLookup = ServiceLocator.Current.GetInstance<ITypeScannerLookup>();
     
                _rootCategory = _categoryRepository.GetRoot();
                _allCategories = _rootCategory.GetList().Cast<Category>().ToList();
            }
     
            /// <summary>
            /// Scan types that inherit from Category.
            /// Save the types as categories in EPiServer.
            /// </summary>
            public void Scan()
            {
                var sourceTypes = _typeScannerLookup.AllTypes.Where(
                    t => typeof(Category).IsAssignableFrom(t) && t != typeof(Category));
                 
                foreach (var type in sourceTypes)
                {
                    var attributes = type.GetCustomAttributes();
                    var categoryTypeAttribute = attributes.FirstOrDefault(a => a is CategoryTypeAttribute) as CategoryTypeAttribute;
     
                    if (categoryTypeAttribute != null)
                    {
                        CreateOrUpdateCategory(categoryTypeAttribute);
                    }
                }
            }

In the Scan method I’m calling the AllTypes property off the ITypeScannerLookup. This is a IEnumerable<Type> and in this collection I search for all types that inherit from Category. This will give me a list of the types: ProductTypeCategory, JeansCategory, ShirtsCategory, ShirtsShortSleeveCategory, ShortsLongSleeveCategory.

Next step is to save the types as categories in EPiServer. The information about the category is set on the class attribute, as you can see in the previously code snippet. On the CategoryTypeAttribute the GUID is used as a reference to the category class and the category in the database. Besides of the GUID also the Name, Description and Parent is used to save the category in EPiServer.

private void CreateOrUpdateCategory(CategoryTypeAttribute categoryTypeAttribute)
            {
                Category category = _allCategories.FirstOrDefault(c => c.GUID == new Guid(categoryTypeAttribute.Guid));
     
                if (category == null)
                {
                    category = new Category();
                    category.GUID = new Guid(categoryTypeAttribute.Guid);
                    _allCategories.Add(category);
                }
                else
                {
                    category = category.CreateWritableClone();
                }
                category.Name = categoryTypeAttribute.Name;
                category.Description = categoryTypeAttribute.Description;
                category.Parent = GetParentCategory(categoryTypeAttribute.Parent);
     
                _categoryRepository.Save(category);
            }

 Finally, I have created an extension method to get all child pages that contains one or more categories. I can call the GetChildren method and pass a parent ContentReference and an array of Category types.

contentRepository.GetChildren<ArticlePage>(ContentReference.StartPage, new[] {typeof (JeansCategory)});
    

 This is how the implementation looks like:

public static IEnumerable<T> GetChildren<T>(this IContentRepository contentRepository,
                ContentReference contentReference, Type[] categoryTypes) where T : PageData
            {
                var categoryIds = categoryTypes.Select(GetCategoryIdFromType);
     
                return contentRepository.GetChildren<T>(contentReference)
                    .Where(c => c.Category.MemberOfAny(categoryIds));
            }
     
            private static int GetCategoryIdFromType(Type type)
            {
                var attributes = type.GetCustomAttributes(false);
     
                var categoryTypeAttribute = attributes.OfType<CategoryTypeAttribute>().FirstOrDefault(a => a is CategoryTypeAttribute);
                if (categoryTypeAttribute != null)
                {
                    return _allCategories.FirstOrDefault(c => c.GUID == new Guid(categoryTypeAttribute.Guid)).ID;
                }
                return -1;
            }

 

EPiServer self uses the same concept with content types. If you want to create a new content type you can also do this from code. Simply by creating a new class that is decorated with the ContentTypeAttribute which contains information about the content type. EPiServer uses the GUID property to hold a reference to the content type stored in the database.

 

 

 

Comments