Applying Helix code analysis to GitHub repos – Step by step

Whilst working alongside Rad Kozlowski, he recently created a GitHub action that can be applied to any Helix Github repo. This runs an automated analysis on the source to check for Helix compliance. HelixCheck was launched and I have set this up on a Helix Base feature branch with a tutorial below:

Step 1: In your GitHub repository select the ‘Actions’ tab. Here you will see a list of actions that are running on this repository:
Step 2: With your current actions visible, select ‘New workflow’, this will open the following screen where we will select ‘set up a workflow yourself’:
Step 3: This will open the new workflow screen:
Step 4: Delete the contents of this screen and replace with the HelixCheck action, I’ve added descriptive comments:
Below is the code to copy and paste, you can also find the file on Helix Base:
name: Helix Check on 9.2 branch

# Controls when the action will run. HelixCheck workflow will run on push
# events but only for the feature 9.2 branch 
on:
  push:
    branches: [ feature/9.2.0 ]

# Set up the HelixCheck job
jobs:
  check_job:
    name: Helix check
    runs-on: ubuntu-latest

# Check out the repo
    steps:
      - name: Checkout
        uses: actions/checkout@v2

# Configure the Helix Check step
      - name: Helix Check
        uses: ethisysltd/helix-check@v1.0
        id: check
        with:
          solution-file: 'Helixbase.sln' # Set the name of your solution file here
          project-name: 'Helixbase' # Add the project name here. Helix Base naming is Helixbase.Feature.Hero for example so the project name is 'Helixbase'
          website-folder: 'website' # Older versions of Helix will use 'code' here, newer is 'website'
      
      # Output the HelixCheck results
      - name: Get the check result
        run: echo "Check result - ${{ steps.check.outputs.result }}"
      
      - name: Get the output time
        run: echo "The time was - ${{ steps.check.outputs.time }}"

Once you save the action you can then see an output of the results under Actions:

Any issues within the project relating to Helix compliance will now be listed here. This action can be applied to any event or any branch which means automated code analysis on any Helix GitHub repo, excellent work Rad!

Sitecore placeholders as a Droplist

When it comes to selecting a placeholder in Sitecore we are required to enter text into a Single-Line Text field:

placeholder select

Obviously this introduces scope for a typo and potentially isn’t an ideal user experience. So I thought we’d look at changing this to a Droplist. The good news is the first step is a very straightforward task and we only need to update the Standard Rendering Parameters template found at /sitecore/templates/System/Layout/Rendering Parameters/Standard Rendering Parameters

The Placeholder field can be updated to a Droplist with a source query that filters anything that’s not a ‘Placeholder’ template (/sitecore/templates/System/Layout/Placeholder). We can use a query such as:

query:/sitecore/layout/Placeholder Settings//*[@@templateid='{5C547D4E-7111-4995-95B0-6B561751BF2E}’]

Our Standard Rendering Parameters template now appears as follows:

sitecore rendering parameters

And we can now select our placeholders from a Droplist:

sitecore_rendering_params_dropdown

Bear in mind that we have directly updated a Sitecore template, so any platform update will replace this change. We could serialize the Standard Rendering Parameters template but adding a vanilla template to source control is never a good idea. Food for thought.

Sadly our work isn’t quite finished, while this works for the Control Properties Dialog we also need to think about the Select Rendering Dialog incase a user wants to add a rendering from the Device Editor:

sitecore rendering selection

This is a sheer UI control found at:

MYSITE/sitecore/shell/default.aspx?xmlcontrol=Sitecore.Shell.Applications.Dialogs.SelectRendering&hdl=0ECAC17C88F941F08B14CE06796BF366&ro=sitecore%3A%2F%2Fmaster%2F%7BEB2E4FFD-2761-4653-B052-26A64D385227%7D%3Flang%3Den%26ver%3D1&ic=SoftwareV2%2F16x16%2Fcomponent_blue.png&txt=Select%20the%20rendering%20that%20you%20want%20to%20use.%20Click%20Select%20to%20continue.&ti=Select%20a%20Rendering&bt=Select&rt=Id&sph=1&sop=1&str=1

So we need to find the XML Control that uses the class Sitecore.Shell.Applications.Dialogs.SelectRendering

This is found in \sitecore\shell\Applications\Dialogs\SelectRendering\SelectRendering.xml

Rather than manipulate the vanilla XML control, we can copy this file to the ‘override’ folder to ensure that our new control is used:

\sitecore\shell\override\Applications\Dialogs\SelectRendering\SelectRendering.xml

Edit the line:

<CodeBeside Type=”Sitecore.Shell.Applications.Dialogs.SelectRendering.SelectRenderingForm,Sitecore.Client”/>

And change this to the class in your project:

<CodeBeside Type=”Helixbase.Feature.Fun.SelectRenderingForm,Helixbase.Feature.Fun”/>

And for our code behind file we can copy the class ‘SelectRenderingForm’ which is found in Sitecore.Shell.Applications.Dialogs.SelectRendering of the Sitecore.Client.dll (or Sitecore.Shell.dll depending upon your version of Sitecore).

First we need to change the PlaceholderName property from an ‘Edit’ to a ‘Listbox’ type:

protected Listbox PlaceholderName { get; set; }

We then loop through our placeholder items and populate the Listbox (view below or on Gist)

 var database = Sitecore.Configuration.Factory.GetDatabase("master");

            var placeHolderItems= database.SelectItems("/sitecore/layout/Placeholder Settings//*[@@templateid='{5C547D4E-7111-4995-95B0-6B561751BF2E}']");

            foreach (var placeholder in placeHolderItems)
            {
                this.PlaceholderName.Controls.Add(new Sitecore.Web.UI.HtmlControls.ListItem
                {
                    Value = placeholder.Name,
                    Header = placeholder.Name
                });
            }

We can now see placeholders as a Droplist:

sitecore_placehodler_dropdown

Automating item unlocks when deleting Sitecore users

Working with a large team of content editors can present us with difficulties, and one of these problems presents itself when we delete a user. If a user has locked items, deleting the user still leaves their items locked.

Sitecore contains a rule /sitecore/system/Settings/Validation Rules/Item Rules/Security/Locked By Non Existing User and we can use this item rule to give us a visual indicator of which items are locked by none existing users. This can be applied globally by adding it to our Global Rules item found at /sitecore/system/Settings/Validation Rules/Global Rules item.

So we can see which items are locked, and as an individual a content editor can unlock their own files by selecting the ribbon option ‘My Items’ and then using ‘Unlock All Items’ which calls the a method in the Sitecore.Shell.Applications.WebEdit.Commands.UnlockAll class. However, this method executes in the context of the current user rather than our non existing user who has locked the items. So while this method isn’t suitable for what we’re looking for, we can use something similar.

Upon deleting users Sitecore calls the user:deleted event so we need to create a handler for this event. You can see this handler being patched in Helix Base, and the source used by the event but below is the source for the handler:

    public class RemovedUserEventHandler
    {
        public void OnUserDeletedUnlockFiles(object sender, EventArgs args)
        {
            if (EventDisabler.IsActive)
                return;

            var objList = new List<Item>();
            var userName = Event.ExtractParameter<string>(args, 0);

            Assert.IsNotNullOrEmpty(userName, "User name was null or empty");

            var lockedItems = Client.ContentDatabase.SelectItems($"search://*[@__lock='%{userName}%']");

            if (lockedItems == null || !lockedItems.Any())
                return;

            foreach (var lockedItem in lockedItems)
                objList.AddRange(lockedItem.Versions.GetVersions(true).Where(version =>
                    string.Compare(version.Locking.GetOwner(), userName, StringComparison.OrdinalIgnoreCase) == 0));

            ProgressBox.Execute(nameof(RemovedUserEventHandler), "Unlocking items", "Network/16x16/lock.png",
                UnlockAllItems, "lockeditems:refresh", Context.User, objList);

            SheerResponse.Alert($"Successfully unlocked {objList.Count} item(s) checked out by {userName}",
                Array.Empty<string>());
        }

        private static void UnlockAllItems(params object[] parameters)
        {
            Assert.ArgumentNotNull(parameters, nameof(parameters));

            if (!(parameters[0] is List<Item> parameter))
                return;

            var job = Context.Job;

            if (job != null)
                job.Status.Total = parameter.Count;

            foreach (var lockedItem in parameter)
            {
                job?.Status.Messages.Add(Translate.Text("Unlocking {0}", (object)lockedItem.Paths.ContentPath));
                lockedItem.Locking.Unlock();
                if (job != null)
                    ++job.Status.Processed;
            }
        }
    }

Which we then patch in to the event:

  <sitecore>
    <events>
      <event name="user:deleted">
        <handler type="Helixbase.Feature.ItemUnlock.Events.RemovedUserEventHandler, Helixbase.Feature.ItemUnlock" method="OnUserDeletedUnlockFiles" />
      </event>
    </events>
  </sitecore>

Upon deleting users, you should now see their files automatically unlocked.

You can also find this source on GitHub Gist.

Edit… since writing this blog post Michael West has created a useful script for SPE to transfer ownership from one user to another. This can be used in situations where you wish to maintain a record of a previous users locked items.

Using voice commands to add items to Sitecore. IFTTT: Complete walk through

Having purchased a few Google Home devices my mind turned to getting them to work with Sitecore. Wouldn’t it be cool to tell your Google Home or mobile device to interact with the Sitecore environment?! So I setup a POC to do just that, by using a custom route in an API controller we can create Sitecore items by asking Google Home to “Create Sitecore item [ItemName]“:

First visit the IFTTT Platform where we can add our Applet. By selecting ‘New Applet’ we’re first asked to create a Trigger, for this we select ‘Google Assistant’

Google assistant

This will present us with some options:

google trigger

We’ll be using a phrase with a text ingredient, this allows us to say something like “Hey Google, add [ingredient] to my request”. For the POC this ingredient will be an item name which will be used to add an item to Sitecore under /sitecore/content/Home/Google Home

Now we’ll tell Google that our phrase will be “Create Sitecore item [ItemName]:

google assitant sitecore

And we need a reply from the device we’re speaking to:

google assistant sitecore response

Great, so we’ve got an “if this”, now it’s time for a “then that”. Our devices know what we’re going to say and know how to respond, so we need to create our API controller in Sitecore that’s going to create the item for us. The rough example I used is below:

    public class GoogleTestController: SitecoreController
    {
        public ActionResult IFTTT(string itemName)
        {
            using (new Sitecore.SecurityModel.SecurityDisabler())
            {
                var masterDb = Sitecore.Data.Database.GetDatabase("master");

                var template = masterDb.GetItem("/sitecore/templates/System/Templates/Standard template");

                var parentItem = masterDb.GetItem("/sitecore/content/Home/Google Home");

                parentItem.Add(itemName, new Sitecore.Data.Items.TemplateItem(template));

                return Content($"{itemName} added");
            }
        }
    }

And we’ll need our custom route

        public void Process(PipelineArgs args)
        {
            RouteTable.Routes.MapRoute("Feature.GoogleTest", "GoogleTest/{itemName}",
                new { controller = "GoogleTest", action = "IFTTT" });
        }

That’s our Sitecore code setup, now we need to add a webhook so that Google Assistant can talk to our Sitecore API. Under ‘then’ we select ‘Add action’ and select ‘Webhooks’:

ifttt then

This presents us with a dropdown and we can select ‘Make a web request’. These fields should be self explanatory, so the one I’ll focus on is the URL. In the below I have used a sample URL but obviously this needs to be a publicly accessible server with the Sitecore item Web API enabled:

Sitecore google api

Now we’re calling our Sitecore API controller, and sending in the name of the item that the user has asked to create. We could stop there and deploy but let’s select ‘Add action’ again and choose ‘notifications’. We want a phone notification when an item has been added to Sitecore:

ifttt notification sitecore

Give the Applet a title and description and save it. The final step is to enable this Applet on our IFTTT devices, simply select ‘Turn on’:

sitecore ifttt applet.png

Once it’s on we can talk to any of the Google devices on our account. I tested this by saying the following to my Google Home… “Ok Google, create Sitecore item test item” I hear the response “Ok, creating item test item on your Sitecore instance”, you can hear her response on this recording. We can then confirm that our device has added the item to Sitecore:

test item sitecore

We also get a nice phone notification:

sitecore-item-created-ifttt

The possibilities for this one are endless – Sitecore, it’s invading our homes!

Using the OData Item Service in Sitecore 9 – Step by step

Pre Sitecore 9 if we wanted to retrieve Item data we could make use of RESTful services easily with a simple GET HTTP Request. If for example we wanted to get children of an item we could call an endpoint such as:

http://YourInstance/sitecore/api/ssc/item/Some_Guid/children?fields=ItemId,ItemName,TemplateName

In Sitecore 9 we also have the introduction of the  OData Item Service so I thought I’d take us through a step by step.

First go to the following item in the Core database:

/sitecore/system/Settings/Services/API Key

Right click the API Key folder and insert an ‘OData Item API Key’. An explanation of the fields for this item can be found in the documentation but here’s my setup with an explanation:

ODataItemAPIKey

Database – fairly self explanatory, the Sitecore database you’re retrieving an item from.

Search Filter – we can apply search options using an OData filter format, I’ve left this as the default which will return the latest version of items.

CORS Origins – which origins are allowable? Add your required origins separated with a semicolon. As I’m testing this on Helix Base I have added the relevant origin.

AllowedControllers – you may wish to restrict this API Key to certain controllers and can list those here separated with a semicolon. I’m allowing all so have opted for the wildcard *

Impersonation User – you could leave this blank in which case Sitecore will use the default from your Sitecore.Services.AnonymousUser setting. The default is most likely sitecore\ServicesAPI but I’ve added this to the field anyway.

Once we’ve saved the item it’s time to copy the item Id which we can then use in our request headers, or on the endpoint. For example if my item Id is 1234 I could use a Key of sc_apikey  and value 1234 in my headers, or apply it to the endpoint as follows:

http://demo.helixbase/sitecore/api/ssc/aggregate/content/Items(‘/sitecore/content/Home’)?sc_apikey=1234

Using either approach we can then see the fruits of our labour…

SitecorePostman

To see a list of endpoints for the OData Item Service see the Sitecore documentation

Step by step: Adding a project to a Helix solution

When adding projects to our Helix solutions there is an order we must follow or problems can arise. For example, the path to our NuGet package folder in our .proj file can be incorrect if we don’t setup the new project correctly. This can result you becoming the token “it works on my machine” person, and nobody wants to be that guy! So I’ve created a step by step guide:

First we add a solution folder to the layer in which we wish to add our module. In this case we’re adding a module to the Foundation layer:

addfolder

Give your folder a module name, in this instance we’re using ‘SecurityExtensions’. Next create this folder on disk at the correct location:

addfolderlocal

Back in Visual Studio, next we add a project:

AddProject

And this needs to be a web application, observe the name and location as this is an important step and don’t forget to assign a target framework:

addprojectname

Next we’re adding the unit tests project:

addtests

Click ‘OK’. Now we need to make sure the Web.config file doesn’t get published, set the build action to ‘None’:

buildaction

Close Visual Studio. Next we move some folders on disk. First rename the folder that has been automatically created:

renamefolder

And change the name to ‘code’ (one way to avoid this step would be to call your project ‘code’, and then rename the project in VS):

renamefoldercode

Next we need to find our unit tests project which is in the solution root:

movefolder

We move this folder to our SecurityEditor folder:

movefolderTests

Then rename it to ‘Tests’, your structure should appear as follows:

renamefoldertests

Now we can open Visual Studio and will see an error loading the projects because the path has moved. For each each project we need to change the file path, select the path and click the ‘…’ icon:

renameprojects

Point Visual Studio to the correct location:

locateproject

Perform this step for both your project and your tests project. Once this is done right click on each of these projects and select ‘Reload Project’.

Finally we can add a publish profile. Create a file system publish profile:

publishprofile

Select Local IIS as the target location and select your Sitecore site:

targetiis

I usually use a Debug configuration and name the profile ‘Localhost’:

debug

You should now be able to add projects to your Helix solutions.

Sitecore Helix solution from scratch PowerShell script – troubleshooting

Many of us know about a great script created by Akshay Sura to create a Sitecore Helix solution scratch using PowerShell. Due to my machine configuration I had some problems getting the script to run so thought best to post the answer in case anybody else runs into the same issue:

First of all, don’t forget to open the HabitatSolutionScript.ps1 and change the following paths to match your directory (in my case I changed D:\ to C:\):

#paths to the project templates on your system
$global:classTemplate = "D:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ProjectTemplatesCache\CSharp\Windows Root\Windows\1033\ClassLibrary\csClassLibrary.vstemplate"
$global:webTemplate = "D:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ProjectTemplatesCache\CSharp\Web\1033\WebApplicationProject40\EmptyWebApplicationProject40.vstemplate"

After following the instructions and attempting to run the command ‘Lets-Rumble’ from the Package Manager Console I was presented with the following error:

letsrumble

To fix this, open up cmd.exe as administrator and type the following:

cmdfix

You can now restart Visual Studio and try again:

letsrumblevs

Let’s rumble!

Sitecore & Glass.Mapper tip: Replace your attribute mapping

Having previously used it myself, and having seen many instances making use of attribute mapping I thought a tutorial on updating your mapping method would be useful.

A while ago I obtained some knowledge from Mike at Glass.Mapper relating to the type of method used for mappings. Summed up, if we want to follow SOLID design principles, and use Plain Old CLR Object’s as classes we should be using fluent configuration as opposed to attribute mapping. This allows us to keep our models separate from configuration. This topic is also mentioned in the recent Professional Sitecore 8 Development book, however I’d like to offer a minor change to the solution that’s proposed.

You may have setup Glass.Mapper with your Sitecore solution using attribute mapping:

using System;
using Glass.Mapper.Sc.Configuration;
using Glass.Mapper.Sc.Configuration.Attributes;

namespace Solution.Models.Pages
{
    [SitecoreType(TemplateId = Templates.Pages.TestPage.ID)]
    public interface ITestPage
    {
        [SitecoreId]
        Guid Id { get; set; }

        [SitecoreField]
        string PageField { get; set; }
    }
}

The above may look familiar to you and is a working solution but as mentioned we should convert this to fluent configuration, which means removing all Glass attributes from this class and then creating a mapping class. Something like the following would be an immediate improvement:

using Glass.Mapper.Sc.Maps;
using Solution.Models.Pages;

namespace Solution.Models.Configuration
{
    public class ITestPageMap : SitecoreGlassMap<ITestPage>
    {
        public override void Configure()
        {
            Map(config =>
            {
                // Note - Automapping used so the lines below it aren't a requirement unless
                // you have different field names to your property
                config.AutoMap();
                config.TemplateId(Templates.Pages.TestPage.ID);
                config.Id(f => f.Id);
                config.Field(f => f.PageField).FieldName("PageField");
            });
        }
    }
}

We then register our map class in GlassMapperScCustom.cs:

mapsConfigFactory.Add(() => new ITestPageMap());

We could now replace any attribute mapping in our solution. However, the above uses field names which would present us with a problem should a field name change on a template. So we can improve it by replacing the following line:

config.Field(f => f.PageField).FieldName("PageField");

with:

config.Field(f => f.PageField).FieldId(Templates.Pages.TestPage.Fields.PageField);

And store the GUID within another class. That way if a field name changes on a template our mapping will still work. I’d recommend that anybody who is using attribute mapping sets some time aside to perform this update.

Note – if you’re using Glass in a modular architecture be sure to read this post.

VSTS and Unicorn remote sync

When using VSTS Release Management for your Sitecore deployments you may have a build step which executes the Unicorn remote PowerShell module. The Unicorn.psm1 module uses Write-Host syntax which will not be compatible with your build step. To make the module compatible with VSTS you can simply replace Write-Host with Write-Output which will now display correctly in your VSTS log when deploying a build. Your new function should look something like the following:

Function Sync-Unicorn {
	Param(
		[Parameter(Mandatory=$True)]
		[string]$ControlPanelUrl,

		[Parameter(Mandatory=$True)]
		[string]$SharedSecret,

		[string[]]$Configurations,

		[string]$Verb = 'Sync',

		[switch]$NoDebug
	)

	# PARSE THE URL TO REQUEST
	$parsedConfigurations = '' # blank/default = all

	if($Configurations) {
		$parsedConfigurations = ($Configurations) -join "^"
	}

	$url = "{0}?verb={1}&configuration={2}" -f $ControlPanelUrl, $Verb, $parsedConfigurations

	Write-Output "Sync-Unicorn: Preparing authorization for $url"

	# GET AN AUTH CHALLENGE
	$challenge = Get-Challenge -ControlPanelUrl $ControlPanelUrl

	Write-Output "Sync-Unicorn: Received challenge from remote server: $challenge"

	# CREATE A SIGNATURE WITH THE SHARED SECRET AND CHALLENGE
	$signatureService = New-Object MicroCHAP.SignatureService -ArgumentList $SharedSecret

	$signature = $signatureService.CreateSignature($challenge, $url, $null)

	if(-not $NoDebug) {
		Write-Output "Sync-Unicorn: MAC '$($signature.SignatureSource)'"
		Write-Output "Sync-Unicorn: HMAC '$($signature.SignatureHash)'"
		Write-Output "Sync-Unicorn: If you get authorization failures compare the values above to the Sitecore logs."
	}

	Write-Output "Sync-Unicorn: Executing $Verb..."

	# USING THE SIGNATURE, EXECUTE UNICORN
	[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
	$result = Invoke-WebRequest -Uri $url -Headers @{ "X-MC-MAC" = $signature.SignatureHash; "X-MC-Nonce" = $challenge } -TimeoutSec 10800 -UseBasicParsing

	$result.Content
}

Function Get-Challenge {
	Param(
		[Parameter(Mandatory=$True)]
		[string]$ControlPanelUrl
	)

	$url = "$($ControlPanelUrl)?verb=Challenge"

	[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
	$result = Invoke-WebRequest -Uri $url -TimeoutSec 360 -UseBasicParsing

	$result.Content
}

Export-ModuleMember -Function Sync-Unicorn

Try to avoid overwriting the original Unicorn.psm1 or it can always potentially be overwritten.

Sitecore Habitat & Glass.Mapper

I thought it would be good to get Glass.Mapper up and running on the news feature of the Habitat solution (currently Sitecore 8.2 initial release).

Note… This post is now legacy, also since writing this post Michael Edwards has kindly improved this implementation of Glass.Mapper on the Habitat news feature. The source has been left on  GitHub for reference but please now use/view Helix Base for a full and correct implementation.

Let’s change the news listing to use Glass.Mapper, we’ll be making changes to the news repository so after following this guide you will need to update the other news component views (latest news, news article etc), and the tests project.

Hopefully this example can be used as a springboard for you to make an implementation. Taking these things into consideration we can begin, we first need to install the Glass.MapperSc nuget package to the Sitecore.Feature.News project, we’re doing this to use a Glass context per feature. Once this is added, open up Templates.cs and add the following properties:

public const string TemplateID = "{2F75C8AF-35FC-4A88-B585-7595203F442C}";
public const string PageTemplateID = "{B69277AD-E917-4B9F-9136-A12E0A3E462F}";

Next we’ll be making use of the interfaces as models feature of Glass.Mapper. Add a folder called ‘Models’ to the news project, and add an interface called INewsArticle.cs:

using Glass.Mapper.Sc.Configuration;
using Glass.Mapper.Sc.Configuration.Attributes;
using Glass.Mapper.Sc.Fields;
using System;

namespace Sitecore.Feature.News.Models
{
    [SitecoreType(TemplateId = Templates.NewsArticle.TemplateID)]
    public interface INewsArticle
    {
        [SitecoreId]
        Guid ID { get; set; }

        [SitecoreField]
        string NewsTitle { get; set; }

        [SitecoreField]
        DateTime NewsDate { get; set; }

        [SitecoreField]
        Image NewsImage { get; set; }

        [SitecoreField]
        string NewsSummary { get; set; }

        [SitecoreField]
        string NewsBody { get; set; }

        [SitecoreInfo(SitecoreInfoType.Url)]
        string Url { get; set; }

    }
}

Ideally we’d refactor the Url and ID into the foundation project but we’ll continue for now. Before looking at the controller and repository here’s the new source for the view NewsList.cshtml (excuse the formatting, WP won’t seem to accept it):

@using Sitecore.Feature.News.Models
@using Glass.Mapper.Sc.Web.Mvc

@model IEnumerable<INewsArticle>
<ul class="media-list media-list-divided">
    @foreach (var item in Model)
    {
        using (@Html.Glass().BeginEditFrame(item, "News Article", x => x.NewsImage, x => x.NewsDate, x => x.NewsTitle, x => x.NewsSummary))
        {
	<li class="media">
<div class="media-left">
                    <a href="@item.Url">
                        @Html.Glass().Editable(item, x => x.NewsImage, new { Width = 150 })
                    </a></div>
<div class="media-body">
<div class="label label-info">
                        @Html.Glass().Editable(item, x => x.NewsDate, x => x.NewsDate.ToString("MMMM dd,yyyy"))</div>
<h3 class="media-title">
                        <a href="@item.Url">
                            @Html.Glass().Editable(item, x => x.NewsTitle)
                        </a></h3>
@Html.Glass().Editable(item, x => x.NewsSummary)</div></li>
}
    }</ul>

Next we update our controller to inherit GlassController:

public class NewsController : GlassController

And edit the NewsList ActionResult:

        public ActionResult NewsList()
        {
            ISitecoreContext context = new SitecoreContext();

            var model = this.newsRepositoryFactory.Create(context).Get();

            return View("NewsList", model);
        }

Finally let’s update the repository. Here’s our changes to INewsRepositoryFactory.cs:

namespace Sitecore.Feature.News.Repositories
{
    using Glass.Mapper.Sc;

    public interface INewsRepositoryFactory
    {
        INewsRepository Create(ISitecoreContext contextItem);
    }
}

And it’s concrete implementation NewsRepositoryFactory.cs:

namespace Sitecore.Feature.News.Repositories
{
    using System;
    using Sitecore.Foundation.Alerts;
    using Sitecore.Foundation.Alerts.Exceptions;
    using Glass.Mapper.Sc;

    public class NewsRepositoryFactory : INewsRepositoryFactory
    {
        public INewsRepository Create(ISitecoreContext contextItem)
        {
            try
            {
                return new NewsRepository(contextItem);
            }
            catch (ArgumentException ex)
            {
                throw new InvalidDataSourceItemException($"{AlertTexts.InvalidDataSource}", ex);
            }
        }
    }
}

Here’s our new INewsRepository.cs:

namespace Sitecore.Feature.News.Repositories
{
    using System.Collections.Generic;
    using Models;

    public interface INewsRepository
    {
        IEnumerable<INewsArticle> Get();
        IEnumerable<INewsArticle> GetLatestNews(int count);
    }
}

I’ve removed the use of the search service on the NewsRepository.cs as any change would impact all features in the solution, I’ve left it commented out for now but will come back to it at a later date. Here’s what we have for now:

namespace Sitecore.Feature.News.Repositories
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Sitecore.Foundation.Indexing.Models;
    using Sitecore.Foundation.Indexing.Repositories;
    using Glass.Mapper.Sc;
    using Models;

    public class NewsRepository : INewsRepository
    {
        public ISitecoreContext ContextItem { get; set; }

        private readonly ISearchServiceRepository searchServiceRepository;

        public NewsRepository(ISitecoreContext contextItem) : this(contextItem, new SearchServiceRepository(new SearchSettingsBase { Templates = new[] { Templates.NewsArticle.ID } }))
        {
        }

        public NewsRepository(ISitecoreContext contextItem, ISearchServiceRepository searchServiceRepository)
        {
            if (contextItem == null)
            {
                throw new ArgumentNullException(nameof(contextItem));
            }
            //if (!contextItem.IsDerived(Templates.NewsFolder.ID))
            //{
            //  throw new ArgumentException("Item must derive from NewsFolder", nameof(contextItem));
            //}
            this.ContextItem = contextItem;
            this.searchServiceRepository = searchServiceRepository;
        }

        public IEnumerable<INewsArticle> Get()
        {
            //  var searchService = this.searchServiceRepository.Get();
            //  searchService.Settings.Root = this.ContextItem;
            //  //TODO: Refactor for scalability
            //  var results = searchService.FindAll();
            //  return results.Results.Select(x => x.Item).Where(x => x != null).OrderByDescending(i => i[Templates.NewsArticle.Fields.Date]);

            return ContextItem.Query<INewsArticle>("/sitecore/content/Habitat/Home/Modules/Feature/News/News//*[@@templateid='" + Templates.NewsArticle.PageTemplateID + "']").Where(x => x != null).OrderByDescending(i => i.NewsDate);

        }

        public IEnumerable<INewsArticle> GetLatestNews(int count)
        {
            //TODO: Refactor for scalability
            return this.Get().Take(count);
        }
    }
}

Good to go, it’s now a simple task to update the other news component views, controller and the tests project. Here’s our working news list which obviously looks the same!…

glassnewslist