Custom SXA Component with rendering variants - Feature Block List Development in 24 steps

Although Clone rendering in SXA  is a good feature, I feel it is a good option for simple controls but not complex ones like lists for obvious reasons like clone is intended to clone existing functionality as-is and can't expect it to be perfect for your custom requirement. I'll probably update this blog later as to where clone rendering causes issues in case if you attempt to create a Feature Block List from File List and look at steps to make the cloned rendering work.

For now, this blog post is a pictorial step-by-step walk-through of creating a Feature Block List in Sitecore 10.3 SXA using Sitecore documentation + File List component dotpeek code.

Feature Block List or simply, Feature List usually looks like this and understandably this is a very important component, present in most home pages:




Steps at high-level:

- Create data template and rendering parameter template
- Create branch template for variants
- Create Sitecore Controller rendering
- Create SXA module and assign rendering
- Use the branch template to create the variant list under Presentation
- Optionally, set html tag and styles for the variant fields
- In C#, create repository, controller, patch config file, register dependencies and view
- Match view html with html markup 
- Publish c# project to Sitecore instance
- Map c# controller to Sitecore controller rendering
- Create Content data source based on data template
- Use new SXA rendering variant
- Publish site

The whole process can be divided as follows into different sections:


Now, off to the step-by-step approach:

1. Create following templates:

1.1 Under Datasource folder


- Feature Block copied from /sitecore/templates/Feature/Experience Accelerator/Media/Datasource/File

Create following fields:


- Feature Block List copied from /sitecore/templates/Feature/Experience Accelerator/Media/Datasource/File List

Inheritance in action:


- Feature Block List Folder copied from /sitecore/templates/Feature/Experience Accelerator/Media/Datasource/File List Folder

In both of the above cases, ensure proper insert option is set for respective item standard values since if copied from File List or File List Folder, they will point to file-related items instead of correct Feature Block or Feature Block List:

For example, Feature Block List item Standard Values assigned Feature Block item:


1.2 Under Rendering Parameters,  in order to use existing inheritance, create Feature Block List Parameters by copying and renaming /sitecore/templates/Feature/Experience Accelerator/Media/Rendering Parameters/FileListParametersTemplate 


2. Under Templates > Branches, create variants


In other words, here we are create a template for the branch that we will later use to create the actual variant list.

Details:


Ensure that each field name matches template field name:



3. Under Renderings, create controller rendering:


This rendering will point to controller c# code action method (explained down the line).

4. Ensure Datasource Location and Datasource Template point to correct/relevant path if copying from an existing rendering:


5. Also, ensure to set the parameter template created in step 1.2:


6. Create featurelist under Placeholder Settings (probably not so important but following existing File List component flow):


7. Under tenant > site > Presentation > Available Renderings, create an Available Rendering named Custom Renderings and add Feature Block List rendering. 


Ensure Originator is set to branch template variant.

8. Similarly, under /sitecore/content/<tenant>/<site>/Presentation/Rendering Variants, add Variants named Feature Block List 

Then, use the branch template created in step 2 to create the variant tree:


9. Variant list created using branch template under /sitecore/content/<tenant>/<site>/Presentation/Rendering Variants:


In each of the fields, you can add the  html tag and cssclass as per the html pages:



10. Create an empty MVC .Net Framework project and add needed folder structure for controllers, views and models:



11. Install Sitecore.XA.Foundation.RenderingVariants nuget package:



12. Under Repositories, create IFeatureListRepository and add the following interface code to follow SOLID principle in our development methodology (thanks dotpeek):

////////////

using Sitecore.XA.Foundation.IoC;

using Sitecore.XA.Foundation.Mvc.Repositories.Base;

using Sitecore.XA.Foundation.RenderingVariants.Lists.Pagination;


namespace CustomSXA.Feature.CustomRenderings.Repositories

{

    public interface IFeatureListRepository :

    IModelRepository,

    IControllerRepository,

    IAbstractRepository<IRenderingModelBase>

    {

        IRenderingModelBase GetModel(IListPagination paginationConfiguration);

    }

}



//////////




13. Next, Create the FeatureListRepository that fills the model based on the selected datasource and this encapsulated data will be passed onto the controller (thanks dotpeek):

//////////////////////////

using Sitecore.Data.Items;

using Sitecore.XA.Foundation.IoC;

using Sitecore.XA.Foundation.Mvc.Repositories.Base;

using Sitecore.XA.Foundation.RenderingVariants.Lists.Pagination;

using Sitecore.XA.Foundation.RenderingVariants.Models;

using Sitecore.XA.Foundation.RenderingVariants.Repositories;

using System.Collections.Generic;

using System.Linq;


namespace CustomSXA.Feature.CustomRenderings.Repositories

{

    public class FeatureListRepository :

      ListRepository,

      IFeatureListRepository,

      IModelRepository,

      IControllerRepository,

      IAbstractRepository<IRenderingModelBase>

    {

        public override IRenderingModelBase GetModel()

        {

            VariantListsRenderingModel variantListsRenderingModel = new VariantListsRenderingModel();

            this.FillBaseProperties((object)variantListsRenderingModel);

            variantListsRenderingModel.Items = this.GetItems();

            return (IRenderingModelBase)variantListsRenderingModel;

        }


        public IRenderingModelBase GetModel(IListPagination paginationConfiguration)

        {

            VariantListsRenderingModel variantListsRenderingModel = new VariantListsRenderingModel();

            this.FillBaseProperties((object)variantListsRenderingModel);

            variantListsRenderingModel.Items = (IEnumerable<Item>)this.GetItems().Skip<Item>(paginationConfiguration.Skip).Take<Item>(paginationConfiguration.PageSize).ToList<Item>();

            return (IRenderingModelBase)variantListsRenderingModel;

        }

    }

}

/////////////////////////



In the above code, the VariantListsRenderingModel automagically understands your datasource template fields while the view is rendered.

14.a In the controller, add the following code that gets the model from assigned datasource and uses the StandardController base class to invoke the underlying Index action method (thanks dotpeek):

//////////

using CustomSXA.Feature.CustomRenderings.Repositories;

using Sitecore.XA.Foundation.RenderingVariants.Controllers.Base;


namespace CustomSXA.Feature.CustomRenderings.Controllers

{

    public class FeatureListController : PaginableController

    {

        protected IFeatureListRepository FeatureListRepository { get; }


        public FeatureListController(IFeatureListRepository repository) => this.FeatureListRepository = repository;


        protected override object GetModel() => (object)this.FeatureListRepository.GetModel(this.PaginationConfiguration);

    }

}


////////



14.b Create Feature Block List.cshtml under Views\FeatureList and also copy over the styles needed for the component under styles folder in the sln/web root and add link reference in the cshtml as follows:


<site web root>\Views\FileList\File List.cshtml was used as reference to create the above file.

Step 9 in conjunction with above markup will function as follows:


The RenderVariant line above will build the final HTML based on Sitecore Variant definition (in step 9):


Note that styles can be uploaded to media library > theme in case of SXA but keeping the complexity level low by adding/accessing the css/styles from file system in this case.

15. Add App_Start > RegisterDependencies.cs that performs DI:

////////
using CustomSXA.Feature.CustomRenderings.Controllers;
using CustomSXA.Feature.CustomRenderings.Repositories;
using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;

namespace CustomSXA.Feature.CustomRenderings.App_Start
{
    public class RegisterDependencies : IServicesConfigurator
    {
        public void Configure(IServiceCollection serviceCollection)
        {
            serviceCollection.AddTransient<IFeatureListRepository, FeatureListRepository>();
            serviceCollection.AddTransient<FeatureListController>();
        }
    }
}


///////



16. Patch file:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">

  <sitecore>

    <services>

      <configurator type="CustomSXA.Feature.CustomRenderings.App_Start.RegisterDependencies, CustomSXA.Feature.CustomRenderings" />

    </services>

  </sitecore>

</configuration>


17. Create a publish profile and deploy this application to the web root.

18. Back in Sitecore, glue the .net controller to the Sitecore controller rendering:


19. Next, create a data source in Sitecore for the Feature Block:



20. Now, in content editor, open the <tenant> /<site>/ home page in XPE and in the SXA toolbox, should see a module named Custom Renderings and within that a control named Feature Block List since Available renderings is configured in step 7:


21. Drag and drop the new rendering to main placeholder and the content created in step 19 must be visible to pick:



22. The component should be content-editable and visible now with the option to select different variants:


Variants in action:



23.  Select a variant, save and do Sitecore Publish to view the desired end-result in the web page:


24. Based on field style settings in 9, style reference in cshtml in 14.b, if you inspect the page now, they must match the component' markup in the reference html page:


Github


YSOD:

Server Error in '/' Application.

Could not load file or assembly 'System.Text.Encodings.Web' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IO.FileLoadException: Could not load file or assembly 'System.Text.Encodings.Web' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

Source Error:


Solution:

Set Copy Local to False for this dll reference:



Error:

When you drag and drop the component to XPE, throws an error in similar lines:

Error Rendering Controller: CustomSXA.Feature.CustomRenderings.Controllers.FeatureListController,CustomSXA.Feature.CustomRenderings. Action: Index: Could not create controller: 'CustomSXA.Feature.CustomRenderings.Controllers.FeatureListController,CustomSXA.Feature.CustomRenderings'. The item being rendered is: '/sitecore/content/New tenant/New site/Home'. The context item is: '/sitecore/content/New tenant/New site/Home'. The current route url is: '{*pathInfo}'. This is the default Sitecore route which is set up in the 'InitializeRoutes' processor of the 'initialize' pipeline.
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)
   at Sitecore.Mvc.Controllers.ControllerRunner.GetController()
   at Sitecore.Mvc.Controllers.ControllerRunner.Execute(TextWriter writer)
   at Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer.Render(Renderer renderer, TextWriter writer, RenderRenderingArgs args)

Inner Exception: The controller for path '/' was not found or does not implement IController.
   at System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType)
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)

Resolution: Open the Visual Studio Solution and deploy the component to the Sitecore instance.

Comments