0 comments on “Sitecore PaaS and Ansible”

Sitecore PaaS and Ansible

Sitecore PaaS and Azure is a good match and the idea is to blend in Ansible for Sitecore PaaS infrastructure set up on Azure and vanilla Site deployment.

Why would you use Ansible? Using Powershell scripts with parameter files is the common approach. Ansible is a very valid alternate approach for organisations who have Ansible in their tech stack already or for those that prefer it over Powershell.

Let’s start with a brief overview of Ansible. Ansible is an automation tool to orchestrate configuration and deployment of software. Ansible is based on agent less architecture by leveraging the SSH daemon. The Ansible playbook is a well defined collection of scripts that defines the work for server configuration and deployment. They are written in YAML and consists of multiple plays each defining the work to be done for a configuration on a managed server. 

Ansible Playbooks help solve the following problems:

  1. Provision of Azure Infrastructure required to run Sitecore and the deployment of Sitecore. Ansible supports the ability to seperate the provision of the infrastructure from the deployment of the Sitecore packages into “roles”. These roles can then be shared between different playbooks essentially allowing for re-use and the configuration of different playbooks for different purposes.
  2. Modularise the environment spin up into tasks/plays instead of one monolithic command doing everything in one go.
  3. By executing a single playbook, all the required tasks are coordinated to be executed to result in a fully operational instance of Sitecore up and running and ready to be customised by the organisations development team

Ansible Playbooks help with workflow between teams:

  1. Provide flexibility for Developers and DevOps teams to work together on separate piece of work to achieve a common goal. A DevOps team can work on the Azure Infrastructure set up and Developers can work on Application set up and vanilla deployment  
  2. Once the environment is provisioned hand it over to Development team for each site to deploy the custom code and configuration on Vanilla site.

Ansible has list of pre-built modules for Azure that can be leveraged for Azure Infrastructure Spin up and deployment. A list is available here https://docs.ansible.com/ansible/latest/modules/list_of_cloud_modules.html#azure.  

We have used the azure_rm_deployment module during the setup journey. The best thing I liked about Ansible was the ability to structure the parameters in a clean and organised fashion to ensure future extensibility is maintained. Ansible supports the use of multiple parameter file. This allows for both shared and environment specific parameter files. You will see the example later in the blog. 

All the ARM templates, play books and tasks are source controlled and Ansible tower can be hooked into the Source control of your choice.

This allows/enforces all changes to the templates, play books and tasks to be made locally and then commited to the source control repository using familiar tools. Asnible will then retrieve the lastest versions of these files from source control as the initial step on execution.

This option is more streamlined than having to manually upload the updated files to an online repository like a storage account and have Ansible/Azure access them using URLs.

Below is one of the example of the playbook. The roles mentioned here are just an example. You will need more roles for a complete azure infrastructure and Sitecore deployment 

Note the variables {{ env }} and {{ scname }}. They are passed from the Ansible tower job template into the playbook. This variables needs to be configured in the Extra VARIABLES field as shown below in the example job template. 

The env name is your target environment for which you want to spin up the Sitecore Azure environment. This could be dev, test, regression or production and the site name is the name of your website. This allows you to use same playbook to spin up multiple sites for multiple environment based on the extra variables passed in the job template in Ansible tower. This combination forms the path to the yml file which contains the definition of the parameters, per site, per environment. Below is the snapshot of the variable file structure. 

  • Each role in the Playbook is a Play/Task and the naming convention is fairly self-explanatory. 
  • Each task has a yml file and ARM template (json file). However it is not mandatory to have an ARM template for each of the tasks.
  1. Create the resource group just to have the tasks yml file and no arm template. 

2. Create the Redis Cache resource that will contain both the tasks yml file and the ARM template. 

There are tons of resources available in the Azure ARM template repo https://github.com/Azure/azure-quickstart-templates to get you started. You can then customize it to suit your projects requirements. Sitecore ARM templates are a good starting point which you can utilize to get some ideas. The idea is that you can grab snippets from these example to form your own ARM template. 

I will be writing more blogs on Azure and Sitecore so stay tuned.

0 comments on “Creating a custom SXA rendering with variants”

Creating a custom SXA rendering with variants

In this blog post we’ll step through the process of creating a custom SXA rendering with variants.  For this example, we’ll create a rendering that is used to display information about cars.  By using a rendering with variants, we can add a Car rendering to our page, and depending on which variation we choose, we can display it with a different appearance and different fields.  We’ll create a Car rendering with 3 variations:

  • Summary
  • Specifications
  • Full Details

Template

The first thing we need to do is create the template.  Even though some of the variations only display some of the fields, the template will have all of the fields for a car, and the variations will use only the fields they need.

Template

This template was created in /sitecore/templates/Feature/Cars/Car.

Rendering

The next step is to create the rendering.  Ours was created at /sitecore/layout/Renderings/Feature/Cars/Car.  The rendering should be a Controller Rendering.  Set the Controller (this one has been set to “Car”), and set the Controller Action to “Index”.  Set the Datasource Template to be the template we’ve just created (in this case, /sitecore/templates/Feature/Cars/Car), and the Datasource Location to “query:$site/*[@@name=’Data’]/|*[@@templatename=’Car’]”.  It’s important to set “Editable” to true so that the rendering will be available in the Toolbox.

Rendering

To make the rendering appear in the toolbox, we need to add it to Available Renderings.  The Available Renderings folder is  found under the Presentation folder of your SXA site.  First add an Available Renderings item under the Available Renderings folder.  In this case, I’ve added one called Cars.  Once you’ve added the Available Renderings item, click “Edit” on the Renderings field, and add your new rendering (“Car”) to the list of renderings.

AvailableRenderings

It doesn’t really matter what you call the Available Renderings item, or which Available Renderings item our rendering is added to, because the Available Renderings item is simply for filtering/organising the rendering in the content tree.  It doesn’t actually determine where it appears in the Toolbox.  The folder structure of where the rendering is located determines this.  For example, because this rendering is at /sitecore/layout/Renderings/Feature/Cars/Car, it will appear in the Toolbox under a new category of “Cars”.

Toolbox

Rendering Variants

This is where we setup the 3 variations of the rendering.  To start off, create a new Variants item called “Car” under the Presentation/Rendering Variants folder of your SXA site. Under that item, create 3 Variant Definition items for each of the variants we need.

When the variants have been added, they can be selected from the Variant dropdown list in the Experience Editor:

ListVariants

Under each of the Variant Definitions, we need to insert the fields to be displayed.  The desired layout of these fields will determine how we add the fields.  The fields can be grouped into sections, and the order and hierarchy of the fields directly matches the order and hierarchy of the HTML elements that get rendered.  This, coupled with the “Css Class” field allows you to have full control over the layout and appearance of the variant.   The beauty of this is that once this initial setup has been done, you can add as many variants to this rendering as you like without having to write any more code at all.

When adding the fields, it is important that the value of the “Field name”  matches the name of the field in the template.

As an example, the Summary variant has been created using this following structure:

SummaryVariant

The Css Class for the Image field has been set to “col-xs-3”, and the Css Class for the Car Details section has been set to “col-xs-9”.  The Tags for all of these items are using the default of “div”.  This configuration translates to the following HTML:

<div class="component car">
   <div class="component-content">
      <div class="col-xs-3 field-image">
         <img style="margin:0;" src="/-/media/458B8369BE6D40489797D75DDA8BB79D.jpg?h=114&la=en&w=200&hash=641FD08747F050B6AF1779E77E5BF2C24AB2CEC0" alt="" width="200" height="114" />
      </div>
      <div class="col-xs-9">
         <div class="field-make">Holden</div>
         <div class="field-model">Commodore</div>
         <div class="field-year">2007</div>
      </div>
   </div>
</div>

And it looks like this:

SummaryVariantRendered

As an example, we can have a completely different look for the same rendering by selecting the Full Details variant, which has been setup like this:

FullDetailsVariant

And it ends up looking like this:

FullDetailsVariantRendered

There’s a great article that covers this in more detail at http://www.markvanaalst.com/sitecore-experience-accelerator/the-power-of-the-variants/

Code

This section covers the code that needs to be created in your solution.

First add a new Feature module to your solution, then add the following items:

IRepository

using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Repositories
{
   public interface ICarRepository : IVariantsRepository
   {
   }
}

Repository

using YourBusiness.Feature.Cars.Modes;
using Sitecore.XA.Foundation.Mvc.Repositories.Base;
using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Repositories
{
 public class CarRepository : VariantsRepository, ICarRepository
  {
     public override IRenderingModelBase GetModel()
     {
         CarModel model = new CarModel ();
         FillBaseProperties(model);
         return model;
     }
  }
}

Model

No need to add our template fields in this instance

using Sitecore.XA.Foundation.Variants.Abstractions.Models;

namespace YourBusiness.Feature.Cars.Models
{
   public class CarModel : VariantsRenderingModel
   {
   }
}

Controller

Keep in mind that the controller must inherit from Sitecore.XA.Foundation.Mvc.Controllers.StandardController.  The Standard controller will handle the routing with default action result “Index”, so there’s no need to add our own, although we can override “Index” if we need to.

using YourBusiness.Feature.Cars.Repositories;
using Sitecore.XA.Foundation.Mvc.Controllers;
using Sitecore.XA.Foundation.RenderingVarients.Repositories;

namespace YourBusiness.Feature.Cars.Controllers
{
   public class CarController: StandardController
   {
       protected ICarRepository CarRespository
       {   get; set;   }

       protected IVariantsRepository VariantsRespository
       {   get;  set;  }

       public CarController(ICarRepository, carRepository, IVariantsRepository variantsRepository)
       {
           this.CarRespository = carRepository;
           this.VariantsRespository = variantsRepository;
       }

       protected object GetVariantsModel()
       {
           return VariantsRespository.GetModel();
       }

       protected override object GetModel()
       {
          return CarRepository.GetModel();
       }
   }
}

View

It is important that all SXA components should be wrapped in “component” and “component-content” divs. “Model.Attributes” adds styles from the rendering parameters.

@model YourBusiness.Feature.Cars.Models.CarModel

@if (Model.DataSourceItem != null || Html.Sxa().IsEdit)
{
<div>
   <div class="component-content">
      @if (Model.DataSourceItem == null) 
      { 
         @Model.MessageIsEmpty 
      } 
      else 
      { 
         foreach (VariantFieldBase variantField in Model.VariantFields) 
         { 
            @Html.RenderingVariants().RenderVariant(variantField, Model.Item, Model.RenderingWebEditingParams) 
         } 
      }
   </div>
</div>
}

Config

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

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <services>
            <configurator type="YourBusiness.Feature.Cars.Pipelines.RegisterCarServices, YourBusiness.Feature.Cars" />
        </services>
    </sitecore>
</configuration>

]

Pipeline

Here we register the dependencies.

using Microsoft.Extensions.DependencyInjection;
using YourBusiness.Feature.Cars.Repositories;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Pipelines
{
   public class RegisterCarServices : IServicesConfigurator
   {
      public void Configure(IServiceCollection serviceCollection)
      {
         serviceCollection.AddTransient<ICarRepository, CarRepository>();
         serviceCollection.AddTransient<IVariantRepository, VariantRepository>();
      }
   }
}

 

0 comments on “SXA Installation Pitfalls”

SXA Installation Pitfalls

When installing SXA on a fresh Sitecore instance, things will generally go pretty smoothly, however, when installing SXA on an existing Sitecore instance with customisations, it’s not necessarily quite so simple.  Here’s a summary of some of the issues that were encountered when recently installing SXA 1.4 onto a Sitecore 8.2.4 instance.  Hopefully this post will help if you encounter similar issues.

Dependency Injection

In this instance, there was code that was registering dependencies using wildcards.  Specifically, it was adding (among others) assemblies matching the pattern “*.Feature.*”.  This was wrongly picking up SXA assemblies and was giving errors in the Experience Editor like:

Error Rendering Controller: BrowserTitle. Action: Index: Could not create controller: 'BrowserTitle'.

To fix this, the Dependency Injection code was altered to exclude all assemblies matching the pattern “*.XA.*”.

Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll

The Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll file packaged in SXA 1.4 is an older version than what is being used throughout the solution we’re updating, so when trying to install the package, the installation failed halfway through. To fix this, the SXA install package was altered using 7-zip to replace the included DLL with the newer version.

Missing Rendering IDs (no null check)

This issue may be specific to this instance, and might be due to invalid data, but it’s worth mentioning, since SXA is not doing a null check and this problem may crop up for you, too.

When viewing a particular page, the following exception was thrown:

Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.XA.Feature.Composites
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.GetCompositeComponents(XElement layoutXml)
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.Process(GetXmlBasedLayoutDefinitionArgs args)

This turned out to be some renderings that had no ID, and SXA wasn’t doing a null check. The specific xmlLayout is shown below:


Editing the raw values and removing the renderings with a null ID fixes this problem.

To check if there were any other pages with this same problem, a small powershell script was written:

$items = Get-ChildItem -Path "master:/sitecore/content/Consumer/Home" -Recurse
foreach($item in $items) {
$renderings = Get-Rendering -Item $item
foreach($rendering in $renderings) {
if($rendering.ItemID -eq $null) {
Write-Host Item: $item.DisplayName $item.ID
}
}
}

Media Library/Project Folder

The SXA installation wants to create its own “Media Library/Project” folder, using a specific ID. If this item with the specific ID doesn’t exist, SXA will fail when creating a Tenant or Site.   Your instance may already have a folder by this name, in which case it will need to be renamed before the SXA installation, then it’s contents moved into the folder created by SXA.

Duplicate Navigation Controllers

For this instance, when adding the SXA navigation rendering to a page, the following exception occurs:

Multiple types were found that match the controller named 'Navigation'. This can happen if the route that services this request ('{*pathInfo}') does not specify namespaces to search for a controller that matches the request.

There may be other similar controllers in your instance where you get the same problem.

0 comments on “Geocoding Australian postcodes”

Geocoding Australian postcodes

While working on some code that allowed a user to perform a search using only a postcode, I discovered some strange behaviour with the Google Maps API.

According to the Google Maps API documentation (https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering), component filtering allows you to filter by postal_code and country, which would suit this need perfectly. I gave this a try, and upon initial testing, it seemed that this was the solution, however, after further testing, it was found that for some postcodes (specifically, some in NSW and ACT), the geocoding query would return ZERO_RESULTS. Maybe this is because there is an overlap in the 2000 series postcodes ¯\_(ツ)_/¯.

An example of the URL I was using for this is shown below (note that postcode 2022 and country AU will return ZER0_RESULTS):

http://maps.googleapis.com/maps/api/geocode/json?components=country%3aAU%7Cpostal_code:2022&sensor=false&client=your_client&channel=web&language=en&signature=your_signature

There are many examples on Stack Overflow of people using this format to search by postcode and claim this to be the solution, but most of them are either from other countries, where this probably isn’t an issue, or they mustn’t have discovered this issue.

According to the Google Maps API documentation, you can use administrative_area (among other fields) to “influence” results, so I tried adding the state to this field, and I found that this made everything work properly. That means that the following URL will geocode the postcode 2022:

http://maps.googleapis.com/maps/api/geocode/json?&components=country%3aAU%7Cpostal_code%3a2022%7Cadministrative_area%3aNSW&sensor=false&client=your_client&channel=web&language=en&signature=your_signature

The issue I had then was that if the user is searching only using the postcode, I had to find a way to provide the state for that postcode to Google so it could geocode the postcode properly. To do this, I created a function that gives me the state based on a postcode as shown below (although this is not a perfect solution, because new postcodes are added from time to time. Potentially a call to an Australia Post API or similar may work better going forward):


public static string PostcodeToState(int postcode)
{
var postcodes = new List();
postcodes.AddRange(Enumerable.Range(1000,1000).Select(x => new KeyValuePair("NSW",x)));
postcodes.AddRange(Enumerable.Range(2000, 600).Select(x => new KeyValuePair("NSW", x)));
postcodes.AddRange(Enumerable.Range(2619, 280).Select(x => new KeyValuePair("NSW", x)));
postcodes.AddRange(Enumerable.Range(2921, 79).Select(x => new KeyValuePair("NSW", x)));

postcodes.AddRange(Enumerable.Range(200, 100).Select(x => new KeyValuePair("ACT", x)));
postcodes.AddRange(Enumerable.Range(2600, 19).Select(x => new KeyValuePair("ACT", x)));
postcodes.AddRange(Enumerable.Range(2900, 21).Select(x => new KeyValuePair("ACT", x)));

postcodes.AddRange(Enumerable.Range(3000, 1000).Select(x => new KeyValuePair("VIC", x)));
postcodes.AddRange(Enumerable.Range(8000, 1000).Select(x => new KeyValuePair("VIC", x)));

postcodes.AddRange(Enumerable.Range(4000, 1000).Select(x => new KeyValuePair("QLD", x)));
postcodes.AddRange(Enumerable.Range(9000, 1000).Select(x => new KeyValuePair("QLD", x)));

postcodes.AddRange(Enumerable.Range(5000, 1000).Select(x => new KeyValuePair("SA", x)));

postcodes.AddRange(Enumerable.Range(6000, 798).Select(x => new KeyValuePair("WA", x)));
postcodes.AddRange(Enumerable.Range(6800, 200).Select(x => new KeyValuePair("WA", x)));

postcodes.AddRange(Enumerable.Range(7000, 1000).Select(x => new KeyValuePair("TAS", x)));

postcodes.AddRange(Enumerable.Range(800, 200).Select(x => new KeyValuePair("NT", x)));

postcodes.Add(new KeyValuePair("ACT", 2620));
postcodes.Add(new KeyValuePair("NSW", 3644));
postcodes.Add(new KeyValuePair("NSW", 3707));

return postcodes.Where(x => x.Value == postcode).Select(x => x.Key).FirstOrDefault();
}

 

0 comments on “Responsive Images and Sitecore”

Responsive Images and Sitecore

responsive

By February 2018 Australians already spent more than double the amount of time on smartphones than on their desktop1. With the greater variety of devices consumers use to access sites, it’s important to serve images which appropriately cater to those devices.

“Responsive images” describes a technique where an image is served to the browser depending (usually) on the width of the browser window. Desktop browsers would generally receive a larger version of the image with a greater download size, and mobile devices a smaller version better suiting the smaller display size of the device and being quicker to download.

One of our clients has a Sitecore 7.2 site which uses both MVC and legacy code in ASP.NET WebForms. The legacy code had multiple ways of handling image resizing, mostly inline HTML hard coding of dimensions. My aim was to provide a single C# library function that could work both with legacy code and new development and allow consistent results.

imagetag

In the newer part of the website, the designer had used a JavaScript based image processor which would allow the browser to determine the optimal resolution of the image required and only request an appropriately sized image from the server.

As you can see from the above HTML sample each image tag has multiple URLs in the data-srcset attribute. From this list the JavaScript library determines the optimal size for the image, based on device and browser size, and only requests that version of the image from the server.

Sitecore provides a tool to provide the resized image files based on the request URL and also caches these resized images. We created a helper function that could take a Sitecore image as input and return all of the URLs representing valid image sizes as a delimited string. The valid sizes are determined by a setting in a config file. This return value is used to populate the data-srcset attribute of our img.

ImageCodeSample

Sitecore 7.5 and beyond require a hash code to be added to the URL2 to prevent Denial of Service attacks by making numerous requests for images of various sizes. Because our new helper function now provides a centralised and configurable way to deliver the required URL string, this will be quite easy to change after the upgrade.

References

  1. http://www.nielsen.com/au/en/press-room/2018/february-2018-digital-ratings.html
  2. http://www.seanholmesby.com/images-not-resizing-in-sitecore-7-5-sitecore-8-0/
0 comments on “Monitoring and Debugging Interaction Processing in Sitecore 9 on Azure PaaS”

Monitoring and Debugging Interaction Processing in Sitecore 9 on Azure PaaS

When configuring a new instance of Sitecore XP or maintaining an existing one, you may encounter a situation where your interactions report shows far fewer interactions than expected.

low-interactions
Where are my interactions?

One possible cause is interaction processing which hasn’t kept up with the interactions being logged on your website. In some cases this can be so slow that it appears collection, processing, and reporting aren’t working at all. Here are a few things you can look at to help you diagnose your issue.

 

Are interactions being recorded?

SELECT TOP 10 * FROM xdb_collection.Interactions ORDER BY StartDateTime ASC
Run this command in each of your shard databases to see the recent interactions which have been recorded. Compare the interactions being logged with the expected number and frequency of interactions in the environment you’re looking at.

 

How many interactions are waiting to be processed?

SELECT COUNT(*) FROM xdb_processing_pools.InteractionLiveProcessingPool
This command will indicate the number of interactions waiting to be processed. Monitoring the number of records in this table can give you an indication of the number of new records being created and the number of new interactions which are being queued for processing.
If the number of records is steadily building up, either processing isn’t working or it’s working too slowly to handle the workload.
If you’re collecting interactions but not seeing the size of the live interaction processing pool change at all, there might be an issue with aggregation.

If Analytics reports don’t look quite right, there are some things you can try:

Disable device detection

We encountered an issue with slow processing on a recent project. After logging an issue with Sitecore support, they advised:
Device detection has been known to cause the slowness in rebuilding reporting DB.
Try disabling device detection to determine if this has been impacting the speed of processing.

 

Check the CPU usage on your processing role

If you’re consistently seeing a high level of activity, you may need to scale your processing instances up or out.

high-average-cpu
Time for more instances…

Check connection strings

Use the Server Role Configuration Reference to ensure you have the correct settings on each of your servers

Check Application Insights errors

Check in Application Insights for any repeated error messages that might indicate misconfiguration.

 

millions-of-interactions
That’s more like it!

Helpful links

0 comments on “Changing renderings from rendering parameters to datasource”

Changing renderings from rendering parameters to datasource

Recently, for one of our projects, we found that a large number of the client’s renderings were using rendering parameters unnecessarily. In order to allow for personalisation and a better Experience Editor experience, we recommended to the client that they change these renderings to use datasources instead.

The process (which may vary slightly for your instance) went something like this:
For each rendering to be converted:-

  1. Move the related rendering parameters template from its current folder (which in this case was a folder dedicated to rendering parameters) to a more appropriate folder for data item templates.
  2. Make sure that all sub-items are serialised when the template has been moved – I encountered what seems to be a bug with Unicorn, where it doesn’t serialise them. If you get this, you might need to reserialise, or save each sub-item manually.
  3. If the template currently has a name specific to rendering parameters, rename it to something more appropriate.
  4. If required, make sure that the _Standard Values are set.
  5. Where the rest of your site data lives, create a new folder which will be used to store items of the template in question. Set the insert options to add this template.
  6. Change the rendering’s datasource location to be the newly created folder.
  7. Change the rendering to have no rendering parameters template, and instead use a datasource template of the recently moved template.
  8. Remove the “Standard Rendering Parameters” template from the base templates of the new datasource template.
  9. Add a new item to Core DB with the path something like: /content/Applications/WebEdit/Edit Frame Buttons/Signup Form Button/Signup Form
  10. Set Fields for this new item to be a pipe-delimited list of fields that the content editors will need to edit (generally all of them).
  11. In the Visual Studio solution, move the rendering parameters model into the folder where the data items models live, and rename the file and class name if required.
  12. Change the namespace to match the new location.
  13. Inside the controller method (assuming the rendering is a Controller rendering), remove all code that uses the rendering parameters, and instead, set the datasource to be the RenderingContext.Current.Rendering.DataSource.
  14. Change the view using this rendering:
  15. Add a check for if (Model != null), and display a message if in experience editor mode.
  16. Add “using (BeginEditFrame…” and use the path of the folder for the webedit button you added earlier.
  17. Added a check for Sitecore.Foundation.SitecoreExtensions.Extensions.ItemExtensions.IsDerived using a configuration item for the template ID. Display a message if wrong template and skip the rest of the view.
  18. Changed all references to items in the Model be use @Editable(x => x.)

 

0 comments on “Downgrading Helix modules from Sitecore 8.2 to 7.2”

Downgrading Helix modules from Sitecore 8.2 to 7.2

Recently I had to update a Sitecore 7.2 site to use Helix architecture and bring some foundation modules in from a Sitecore 8.2 site.  There were several challenges with this process.  Here’s a few highlights:

Dependency Injection using Castle Windsor

For this site, Castle Windsor was being used for dependency injection.  In the Sitecore 8.2 site, dependencies are registered using a configurator in a .config patch.  This is not supported in Sitecore 7.2, so instead we have to do this in the Application_Start event in global.asax.cs.

In order to be able to use the same DI container throughout the code, I created a singleton container object (ContainerManager.Container):

public static class ContainerManager
 {
   private static IWindsorContainer _container;
   public static IWindsorContainer Container
   {
     get
     {
        if (_container != null) return _container;
        _container = new WindsorContainer();
        _container.Install(FromAssembly.This());
        return _container;
     }
   }
}

Then in global.asax.cs, I used that container to register the dependencies:

protected void Application_Start(object sender, EventArgs e)
 {
    _container = ContainerManager.Container;
    _container.Install(new RegisterGlassDependencies());
    ...

An example of the installer class for registering dependencies is shown below:

 public class RegisterGlassDependencies : IWindsorInstaller
 {
   public void Install(IWindsorContainer container, IConfigurationStore store)
   {
     container.Register(Component.For<ISitecoreContext>().ImplementedBy<SitecoreContext>());
     container.Register(Component.For<IGlassHtml>().ImplementedBy<GlassHtmlTemp>());
     container.Register(Component.For<IGlassFactory>().ImplementedBy<GlassFactory>());
   }
 }

In order for injected constructor parameters to resolve for Controllers, it is necessary to use a custom controller factory which uses the DI container to resolve the controller. There’s a fair bit of overriding going on, so here it comes.

Code for the controller factory shown below:

public class WindsorControllerFactory : DefaultControllerFactory
   {
       private readonly IWindsorContainer _container;
 
       public WindsorControllerFactory(IWindsorContainer container)
       {
           _container = container;
       }
 
       public override void ReleaseController(IController controller)
       {
           _container.Release(controller);
       }
 
       public override IController CreateController(RequestContext requestContext,string controllerName)
       {
           Assert.ArgumentNotNull(requestContext, "requestContext");
           Assert.ArgumentNotNull(controllerName, "controllerName");
           Type controllerType = null;
 
           if (TypeHelper.LooksLikeTypeName(controllerName))
           {
               controllerType = TypeHelper.GetType(controllerName);
           }
 
           if (controllerType == null)
           {
               controllerType = GetControllerType(
                   requestContext,
                   controllerName);
           }
 
           if (controllerType != null)
           {
               return (IController)_container.Resolve(controllerType);
           }
 
           return base.CreateController(requestContext, controllerName);
       }
   }

A pipeline was added with an “instead” patch for Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory.  This pipleine scans all assemblies and gets all classes based on IController then registers them with the Containermanager.Container.  Code shown below:

public class InitializeWindsorControllerFactory
   {
       public virtual void Process(ScapiPipelineArgs args)
       {
           SetupControllerFactory(args);
       }
 
       protected virtual void SetupControllerFactory(ScapiPipelineArgs args)
       {
           var container = ContainerManager.Container;
 
           //TODO: don't use hard-coded filter string
           var assemblies = GetAssemblies.GetByFilter("MyAssembly.*").Where(n => !n.FullName.StartsWith("MyAssembly.Service"))
               .Where(a => GetTypes.GetTypesImplementing<IController>(a).Any(x => x.Namespace != null && x.Namespace.StartsWith("MyNamespace")));
 
           foreach (var assembly in assemblies)
           {
               container.Register(Classes.FromAssembly(assembly).BasedOn<IController>().LifestyleTransient());
           }
 
           var controllerFactory = new WindsorControllerFactory(container);
 
           var scapiSitecoreControllerFactory = new
               ScapiSitecoreControllerFactory(controllerFactory);
 
           ControllerBuilder.Current.SetControllerFactory(scapiSitecoreControllerFactory);
       }
   }

Another pipeline was added with an “instead” patch for Sitecore.Mvc.Pipelines.Response.GetRenderer.GetControllerRenderer.  This pipeline allows us to use a custom controller renderer, as shown below:

public class GetControllerRenderer : Sitecore.Mvc.Pipelines.Response.GetRenderer.GetControllerRenderer
   {
       protected override Renderer GetRenderer(Rendering rendering, Sitecore.Mvc.Pipelines.Response.GetRenderer.GetRendererArgs args)
       {
           var renderer = base.GetRenderer(rendering, args);
           return !(renderer is ControllerRenderer) ? renderer : new CustomControllerRenderer(renderer as ControllerRenderer);
       }
   }

The custom controller renderer then allows us to use a custom controller runner as shown below:

public sealed class CustomControllerRenderer : ControllerRenderer
   {
       public CustomControllerRenderer(ControllerRenderer renderer)
       {
           ControllerName = renderer.ControllerName;
           ActionName = renderer.ActionName;
       }
 
       public override void Render(System.IO.TextWriter writer)
       {
           var controllerName = ControllerName;
           var actionName = ActionName;
           if (controllerName.IsWhiteSpaceOrNull() || actionName.IsWhiteSpaceOrNull())
           {
               return;
           }
           var controllerRunner = new CustomControllerRunner(controllerName, actionName);
           var value = controllerRunner.Execute();
           if (value.IsEmptyOrNull())
           {
               return;
           }
           writer.Write(value);
       }
 
   }

The custom controller runner then creates the controller using our custom controller factory with our DI container as a parameter.  This is our goal. Custom controller runner shown below:

public class CustomControllerRunner : ControllerRunner
   {
       public CustomControllerRunner(string controllerName, string actionName)
           : base(controllerName, actionName)
       { }
 
       protected override IController CreateController()
       {
           return CreateControllerUsingFactory();
       }
 
       private IController CreateControllerUsingFactory()
       {
           NeedRelease = true;
 
           var controllerFactory = new WindsorControllerFactory(ContainerManager.Container);
           return controllerFactory.CreateController(PageContext.Current.RequestContext, ControllerName);
       }
   }

There’s a few layers to it, but we got there in the end.

XUnit Tests

The tests that I moved over from the Sitecore 8.2 site used XUnit.  After moving them over, one of the errors I got in the Sitecore 7.2 site was:

Could not resolve type name: Sitecore.Data.DefaultDatabase, Sitecore.Kernel

To fix this,  for Sitecore versions prior to 8.2 all instances of ‘Sitecore.Data.DefaultDatabase, Sitecore.Kernel’ in the config files should be changed to ‘Sitecore.Data.Database, Sitecore.Kernel’.

One bit I got stuck on for a while was that there was a config file with this string in it that had not yet been added to the solution, but was there on the file system, and that was enough for this error to still be thrown.  It took a file system search for the string to track it down.

 

 

0 comments on “Advanced Scheduled Publishing”

Advanced Scheduled Publishing

Why do we want to schedule publishing?

  1. We want to schedule publishing to ensure that content scheduled to go live will go live when expected. By default, if a scheduled publish isn’t setup any content scheduled to go live will have to wait until the next manual publish after the scheduled date/time.
  2. We want to remove the publish option from editors so that publishing isn’t over utilised.
  3. Reducing the amount of publishes in a day reduces the number of times the Sitecore HTML cache is cleared and therefore increase site performance.

The out of the box scheduled publishing agent provided by Sitecore solves point 1 and 2 above but the issue is publishing at times when a publish is not required. The default publishing agent will publish every x interval all day. This is not efficient because it is publishing when possibly no changes have occurred and that will still cause the HTML cache to clear.

Our Advanced Scheduled Publishing Module provides the option to schedule a publish between a start and finish date and set the interval. For example 9-5 and every 60 minutes.

It also provides the option to set up one-off scheduled publishes at a specific time. So, for example, one publish at 2pm and one at 2am.

These two options can be used individually or in combination. In combination you can allow for a common scenario where we want to publish every 60mins from 9am – 6pm and then a one-off publish at 12am so that any content scheduled to be published will do so ready for the new day.

The configuration of these intervals are managed via a configuration item you create and manage in Sitecore. The documentation found here. The last publish time is updated here also, allowing editors to have more visibility on when the last publish occurred.

Our module is built on top of the Sitecore scheduled tasks so it will check for a time and interval within the range of the scheduled task frequency so, therefore, could occur slightly earlier or later depending on your frequency value. This is a well-documented limitation as the Sitecore scheduled tasks run within the context of a web application which could go down, or be taken down at any time.

Links

Sitecore Marketplace

Github repo

 

0 comments on “Getting settings from Sitecore config that aren’t in the settings section”

Getting settings from Sitecore config that aren’t in the settings section

Recently I had to write some code that got the value of the scheduling frequency in Sitecore config.  Normally, to get a value from Sitecore config, I’d simply use:

Sitecore.Configuration.Settings.GetSetting("yoursettingsname")

However, in this case, the value that I want is not in the <settings> node and the above code won’t work.  The value I want is in the <scheduling> node:

 <!-- SCHEDULING -->
 <scheduling>
 <!-- Time between checking for scheduled tasks waiting to execute -->
 <frequency>00:05:00</frequency>

To get around this, instead, I used:

Sitecore.Configuration.Factory.GetConfigNode("scheduling/frequency")?.InnerXml

The above code should work for any Sitecore config item just by specifying the correct Xpath location for the value you require.