<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Corey’s Substack]]></title><description><![CDATA[My personal Substack]]></description><link>https://www.corey-mcclelland.com</link><image><url>https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png</url><title>Corey’s Substack</title><link>https://www.corey-mcclelland.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 09 May 2026 12:09:19 GMT</lastBuildDate><atom:link href="https://www.corey-mcclelland.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Corey McClelland]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[coreymcclelland@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[coreymcclelland@substack.com]]></itunes:email><itunes:name><![CDATA[Corey McClelland]]></itunes:name></itunes:owner><itunes:author><![CDATA[Corey McClelland]]></itunes:author><googleplay:owner><![CDATA[coreymcclelland@substack.com]]></googleplay:owner><googleplay:email><![CDATA[coreymcclelland@substack.com]]></googleplay:email><googleplay:author><![CDATA[Corey McClelland]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Sitecore Items with Too Many Children]]></title><description><![CDATA[How to find items with too many sub-items the easy way]]></description><link>https://www.corey-mcclelland.com/p/sitecore-items-with-too-many-children</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/sitecore-items-with-too-many-children</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Fri, 19 Sep 2025 19:52:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sitecore generally recommends that items in the content tree not exceed more than around 100 items. Exceeding that can cause performance issues and Sitecore recommends that you convert those items into a bucket. An easy way to identify items with too many children is to search in SQL:</p><pre><code>select *
&#9;from items i
&#9;left join (SELECT ParentID, COUNT(*) AS ChildCount
&#9;&#9;FROM items
&#9;&#9;WHERE ParentID IS NOT NULL
&#9;&#9;GROUP BY ParentID
&#9;&#9;HAVING COUNT(*) &gt; 100) p on p.ParentID = i.ID
&#9;&#9;where p.ParentID is not null
&#9;order by p.ChildCount desc</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[An Easy Way to Make a SQL Table Read Only]]></title><description><![CDATA[Here&#8217;s a very simple way to make a SQL table read only (except for deletes) using a constraint.]]></description><link>https://www.corey-mcclelland.com/p/an-easy-way-to-make-a-sql-table-read</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/an-easy-way-to-make-a-sql-table-read</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Thu, 17 Jul 2025 15:31:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Here&#8217;s a very simple way to make a SQL table read only (except for deletes) using a constraint. It adds the always-failing constraint with <code>nocheck</code> so that it doesn&#8217;t fail existing records.</p><pre><code>ALTER TABLE VisitGeoIpData WITH NOCHECK ADD CONSTRAINT chk_read_only CHECK( 1 = 0 )</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Git Branch in Powershell Prompt]]></title><description><![CDATA[If you&#8217;ve ever used Git Bash before, you&#8217;ve likely seen how it shows the current branch name as part of the command prompt.]]></description><link>https://www.corey-mcclelland.com/p/git-branch-in-powershell-prompt</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/git-branch-in-powershell-prompt</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Wed, 16 Jul 2025 00:35:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve ever used Git Bash before, you&#8217;ve likely seen how it shows the current branch name as part of the command prompt. You can get the same functionality in Powershell with a very simple script.</p><p>Update your Powershell profile by opening this file:</p><p><code>notepad $PROFILE</code></p><p>Add the following script:</p><pre><code>function prompt {
    $currentPath = Get-Location
    $gitBranch = ''

    if (Get-Command git -ErrorAction SilentlyContinue) {
        try {
            $branch = git rev-parse --abbrev-ref HEAD 2&gt;$null
            if ($branch -and $branch -ne 'HEAD') {
                $gitBranch = " [$branch]"
            }
        } catch {
            # Not in a Git repo, do nothing
        }
    }

    return "$currentPath$gitBranch&gt; "
}</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Realtime View of Sitecore development logs]]></title><description><![CDATA[In linux environments I typically monitor logs using the tail command.]]></description><link>https://www.corey-mcclelland.com/p/realtime-view-of-sitecore-development</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/realtime-view-of-sitecore-development</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Mon, 14 Jul 2025 12:27:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In linux environments I typically monitor logs using the <code>tail</code> command. It&#8217;s often installed by default and proves quite useful for viewing the latest log entries in realtime.</p><p>Windows, on the other hand, does not come with this functionality. Here&#8217;s a simple powershell script that works very similar to the tail command and has highlighting enabled for typical log4j logs. It loads the latest log file the given prefix.  In this case it will load the latest log file starting with the <code>Sitecore</code> prefix.</p><p>The script is called like this: <code>.\tail.ps1 .\App_Data\logs\Sitecore</code></p><pre><code>param (
    [Parameter(Mandatory = $true)]
    [string]$Stem
)

function Get-LatestLogFile {
    $directory = Split-Path $Stem
    $filename = Split-Path $Stem -Leaf
    $pattern = "$filename*.log"

    $files = Get-ChildItem -Path $directory -Filter $pattern -File | Sort-Object LastWriteTime -Descending
    if ($files.Count -eq 0) {
        Write-Host "No log files found with stem '$Stem'" -ForegroundColor Red
        exit 1
    }
    return $files[0].FullName
}

function Write-ColoredEntry {
    param ([string[]]$entry)

    $text = $entry -join "`n"

    switch -Regex ($text) {
        '\bFATAL\b' { Write-Host $text -ForegroundColor Magenta; break }
        '\bERROR\b' { Write-Host $text -ForegroundColor Red; break }
        '\bWARN\b'  { Write-Host $text -ForegroundColor Yellow; break }
        '\bDEBUG\b' { Write-Host $text -ForegroundColor Cyan; break }
        default     { Write-Host $text }
    }
}

function Tail-Follow {
    param (
        [string]$FilePath,
        [int]$Lines = 10,
        [int]$Interval = 1
    )

    $encoding = [System.Text.Encoding]::UTF8
    $fs = [System.IO.File]::Open($FilePath, 'Open', 'Read', 'ReadWrite')
    $reader = New-Object System.IO.StreamReader($fs, $encoding)

    # Read last N lines
    $content = Get-Content -Path $FilePath -Tail $Lines
    $buffer = @()
    foreach ($line in $content) {
        if ($line -match '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}') {
            if ($buffer.Count -gt 0) {
                Write-ColoredEntry -entry $buffer
                $buffer = @()
            }
        }
        $buffer += $line
    }
    if ($buffer.Count -gt 0) {
        Write-ColoredEntry -entry $buffer
    }

    # Move to end of file
    $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::End) | Out-Null

    $entryBuffer = @()
    while ($true) {
        $line = $reader.ReadLine()
        if ($line -ne $null) {
            if ($line -match '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}') {
                if ($entryBuffer.Count -gt 0) {
                    Write-ColoredEntry -entry $entryBuffer
                    $entryBuffer = @()
                }
            }
            $entryBuffer += $line
        } else {
            if ($entryBuffer.Count -gt 0) {
                Write-ColoredEntry -entry $entryBuffer
                $entryBuffer = @()
            }
            Start-Sleep -Seconds $Interval
        }
    }
}

# Main
$logFile = Get-LatestLogFile
Write-Host "Following log file: $logFile" -ForegroundColor Cyan
Tail-Follow -FilePath $logFile</code></pre>]]></content:encoded></item><item><title><![CDATA[Why Every Sitecore Implementation Needs Distributed Tracing]]></title><description><![CDATA[Thoughts from a software engineer that has spent 10+ years architecting, building, enhancing, and upgrading Sitecore implementations]]></description><link>https://www.corey-mcclelland.com/p/why-every-sitecore-implementation</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/why-every-sitecore-implementation</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Mon, 31 Mar 2025 14:16:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>The Problem</h1><p>An error is happening in production. The marketing team has just launched a major email campaign and the new landing page is displaying an error instead of the new call to action component. Everything looked great in the development, integration, and pre-production environments. Even the production environment passed through QA and was signed off on before the marketing campaign went live. What happened?</p><p>At this point engineers are going through the production logs to try and identify what's happening and determine a root cause. A stand-alone Sitecore XM environment has, at a minimum, 1x Content Management instance, 2x Content Delivery instances, a SQL cluster, Redis for caching, identity servers for processing logins, SOLR instances for search, etc. Add in a lot more if you're running an XP version of Sitecore. That's a lot of server logs to review for an incident in progress.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Having a log aggregation service like Splunk can help, but you're often still searching for the needle in a haystack. Often that haystack can contain thousands of log entries per second. And try not to think about what Splunk costs to keep all of those logs for any meaningful length of time.</p><p>The other challenge is that any issue occurring anywhere in the enterprise that serves content to Sitecore could actually be the culprit behind the production issue. Frequently, it's a microservice issue with another team that is propagating to the website. This is usually something like a user profile service or an order history service experiencing problems. It doesn't even have to be a complete outage of those services&#8212;increased latency in requests to them could trigger cascading issues. The fix could be as simple as notifying the relevant team about the urgency of scaling their service (microservices can be multiple tiers deep, and the team may not know where their data is being used or how critical it is to your customer-facing application).</p><p>So how do we identify the root cause of a production issue quickly?</p><h2>Warnings, Errors, and Log Levels</h2><p>Server logs grow over time (obviously). The rate at which servers create logs also tends to increase over time. Pretty much every service has many different log levels. A log level is essentially how much data is logged for every action that service takes. The most common log levels are Error, Warn, Info, Debug, and Trace. Each level adds significantly more log data than the previous level.</p><p>The Error level logs only actual errors that have occurred directly in this service itself. On the other hand, the Trace level logs literally everything that happens on every request. Usually, services start out at either the Warn or the Info log level. It's not unusual for this log level to move toward the Debug or even Trace log levels as challenging issues that are hard to debug occur over time. This typically happens to different services at different times.</p><p>These log level changes are similar to technical debt in that everyone knows that a server shouldn't be left at the Debug or Trace log level. It's often out of sight and out of mind though. It can even be helpful for a time as it can help identify issues faster. Over time though, the data keeps growing, and it becomes much more challenging to find the signal in the noise of all the log data.</p><h2>The Solution</h2><p>The solution is to implement Distributed Tracing. Distributed tracing is a technique used to monitor and analyze requests as they travel across a distributed system (such as a microservices architecture). It helps in tracking the flow of requests between different services, identifying performance bottlenecks, diagnosing errors, and understanding system behavior. It is the key that ties the logs between all of the different services together.</p><p>Each request into the system gets assigned a unique trace ID. As each request moves within a service, span IDs can be added to identify individual operations within that service. As one microservice calls another, it includes the trace ID in the request header. This allows troubleshooters to quickly see that the Warn log on the Content Delivery server is directly tied to a timeout Error log in the User Profile microservice.</p><p>This approach does require buy-in throughout the organization, as each application and service needs to be updated to support distributed tracing. The effort level is typically fairly low though, as the libraries that implement distributed tracing quite typically work at a low level and often require minimal configuration out of the box.</p><h1>Common Distributed Tracing Libraries and Tools</h1><p>Here are some popular distributed tracing libraries and frameworks that you can implement in your system:</p><ol><li><p><strong>OpenTelemetry</strong> - An observability framework that merged OpenTracing and OpenCensus; provides language-specific SDKs for various programming environments</p></li><li><p><strong>Jaeger</strong> - An open-source, end-to-end distributed tracing system that's compatible with OpenTelemetry and the OpenTracing standard</p></li><li><p><strong>Zipkin</strong> - One of the earliest open-source distributed tracing systems, inspired by Google's Dapper paper</p></li><li><p><strong>AWS X-Ray</strong> - AWS's distributed tracing service that helps analyze and debug distributed applications</p></li><li><p><strong>Azure Application Insights</strong> - Microsoft's APM service that includes distributed tracing capabilities</p></li><li><p><strong>Datadog APM</strong> - A commercial APM solution with strong distributed tracing features</p></li><li><p><strong>New Relic Distributed Tracing</strong> - Part of New Relic's observability platform</p></li><li><p><strong>Elastic APM</strong> - Distributed tracing integrated with the Elastic Stack</p></li><li><p><strong>Dynatrace PurePath</strong> - Dynatrace's distributed tracing technology</p></li><li><p><strong>Honeycomb</strong> - A commercial observability tool with powerful distributed tracing capabilities</p></li></ol><p>Most of these tools follow the W3C Trace Context standard, which defines a standard format for propagating distributed trace context between services. This makes them relatively interoperable and easier to adopt.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[What does a great Sitecore implementation look like?]]></title><description><![CDATA[Thoughts from a software engineer that has spent 10+ years architecting, building, enhancing, and upgrading Sitecore implementations]]></description><link>https://www.corey-mcclelland.com/p/what-does-a-great-sitecore-implementation</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/what-does-a-great-sitecore-implementation</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Thu, 06 Mar 2025 16:05:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Traditional Sitecore vs Frameworks</h1><p>Traditional Sitecore MVC applications start at a much lower level than newer frameworks like Sitecore SXA or XM Cloud that have been built on top of Sitecore MVC. There is no base application to start from and the Sitecore Information Architecture (IA) is for all intents and purposes empty. While this gives the development team incredible freedom to build out the implementation as they wish, it also leaves the team wide open to issues that can be challenging to fix toward the end of an implementation. These development choices can often impact the user experience for content authors as well as limit the out of the box functionality that Sitecore provides.</p><p>This article will review common issues that I&#8217;ve seen in more than 10 years of building and maintaining Sitecore applications. This isn&#8217;t to say that any implementation that has some of these issues is a bad implementation. It&#8217;s more of a list of red flags to look for to get a better feel for where the implementation is today.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>What a great MVC implementation looks like</h1><p>A great implementation can be a joy to work on.  It&#8217;s intuitive, follows consistent patterns, and it&#8217;s easy to add features or resolve issues. I&#8217;ve found that there are 5 key areas of an implementation that can make or break the development experience: workstation setup, dependency management, the information architecture, the solution architecture, and hotfixes.</p><h2>Workstation Setup</h2><p>A new developer on a project should be able to spin up a development environment very quickly. A developer on a great implementation can be up and running within 15 to 20 minutes of getting started. A developer working on a less than optimal solution can take multiple days to get up and running.</p><h3>Easy Startup</h3><p>The best implementations have the bootstrap and startup processes scripted. A new developer shouldn&#8217;t have to know every detail of the implementation just to get up and running. Older implementations often have a bootstrap script that verifies system dependencies, installs Sitecore, and synchronizes serialized templates and content into the databases. This lets them hit the ground running with an up to date environment very quickly.</p><p>More recent versions of Sitecore are built around Docker containers. Docker containers can be thought of like virtual machines that are built via scripts that the development team create. This allows for an even better development experience as whenever the underlying containers are updated, each development machine gets the updates as well. As all of the dependencies for Sitecore are in the containers, it&#8217;s as easy as running <code>start.ps1</code> script to be up and running with the latest version.</p><p>If on the other hand developers are asking about database backups, Sitecore installation errors, login credentials, or serialization errors you may have some startup issues to resolve.</p><h3>Documentation</h3><p>Great implementations have a <code>Readme.md</code> file in the codebase. This documentation isn&#8217;t to go into great depth about the implementation.  Rather it&#8217;s documentation on getting environments up and running as well as high level information about the solution in general. It&#8217;s also a great place for documenting runtime options and environment variable options. </p><h3>Content Data Updates</h3><p>Templates in Sitecore are expected to be in source control. The content items defined by the templates are typically not stored in source control as it can be a very large amount of files.  Typically there is sample content stored in source control that provides enough content to launch and demo the functionality of the site.</p><p>What happens though when a bug is reported in production that isn&#8217;t reproducible with development content? Content needs to be synchronized from a higher level environment down to a developer&#8217;s workstation. In modern environments this would be handled via the Sitecore command line interface (CLI). It can be helpful to keep the CLI environment and configurations stored in a separate repository to keep the primary repository clean.</p><p>This approach replaces the outdated technique of using Sitecore packages to pass templates and/or content items between environments. Creating packages typically takes longer and is more error prone than using the CLI.</p><h2>Dependency Management</h2><p>Dependency management is critically important for any software solution but there are some unique considerations for Sitecore implementations. This includes library references, Sitecore references, third party dependencies, and Sitecore content packages (historically provided by Sitecore).</p><h3>Versioning</h3><p>Sitecore as a product is unlike many other enterprise applications in that any code modifications are deployed on top of the existing deployment. This leads to the overwriting of the out of the box DLL and/or configuration files with ones from the developer&#8217;s solution/deployment artifacts. It&#8217;s important DLL versions remain consistent with the Sitecore versions.</p><p>This can be problematic when installing nuget packages that have different requirements than the deployed Sitecore versions. It&#8217;s typically common libraries like <code>NewtownSoft.Json</code> that are impacted. Care should be taken to confirm that changing the version doesn&#8217;t introduce breaking changes within Sitecore. I would also typically expect to see a <code>Packages.props</code> file that defines the specific versions of libraries used throughout the solution.</p><p>While it&#8217;s not uncommon to see some version changes in an implementation, it would be a red flag to see many DLLs with inconsistent versions. You can find the assembly version in the release notes for a particular Sitecore release.</p><h3>Sitecore Dependencies</h3><p>Older implementations would typically have a source controlled version of the Sitecore reference DLLs like <code>Sitecore.Kernel.dll</code>. Modern implementations will use package references that refer directly to Sitecore&#8217;s nuget repository. Keeping DLL dependencies within source control can be problematic as it both increases the size of the repository as well as complicates paths to those libraries.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gdtz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gdtz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 424w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 848w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 1272w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gdtz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png" width="711" height="52" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:52,&quot;width&quot;:711,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:23896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.corey-mcclelland.com/i/157963347?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Gdtz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 424w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 848w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 1272w, https://substackcdn.com/image/fetch/$s_!Gdtz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7376a95-e4f3-4d7e-a88c-523ae056ce62_711x52.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Third Party or Private Dependencies</h3><p>Third party libraries that don&#8217;t have public nuget feeds should be stored in a enterprise hosted nuget repository. This includes any hotfix DLLs provided by Sitecore. Azure DevOps provides <a href="https://learn.microsoft.com/en-us/azure/devops/artifacts/get-started-nuget?view=azure-devops&amp;tabs=windows#-create-a-feed">nuget repository hosting</a> called Azure Artifacts that can be used for this purpose.</p><p>This allows for the third party DLLs to be deployed as part of the solution when included as a package reference.</p><h2>Information Architecture</h2><p>Sitecore&#8217;s Information Architecture (IA) is extremely limited out of the box. While this on one hand allows for an extremely flexible IA, it often results in unforeseen complications down the road. Often times these complications relate to adding additional sites. Does the IA support regional sites? Or what about separate sites per language? How is content shared between sites? All of these are directly impacted by the IA and it can be challenging to change when the issues actually arise.</p><h3>Site Structure</h3><p>A good Information Architecture (IA) in Sitecore clearly organizes your site&#8217;s content, structure, and data in a logical, scalable, and maintainable manner. The content is structured around how content authors use and navigate the content rather than how an organization is structured internally. A good Sitecore implementation typically has well thought out approaches for multisite, shared content, data sourcing, external content, and template design.</p><h3>Shared Content</h3><p>Shared content is any content that is typically shared between sites.  This type of content could be anything from press releases, news articles, or even shared taxonomy.</p><h3>Data Sourcing</h3><p>A Sitecore page is comprised of a Layout and Renderings.  Each layout has many renderings on it.  The header, the footer, call to actions (CTAs), text areas, etc are all typically renderings (often referred to as components). Sitecore&#8217;s magic is it&#8217;s ability to change the content of renderings based upon all types of personalization rules that can truly provide unique experiences to site visitors. This magic is powered through the use of rendering data sources.</p><p>The data source on a rendering points to a content item that could many types of content like text fields or images. They are typically built with the rendering in mind.  A CTA content item might contain an image field, a text field, and a target url field.  That would allow content authors to dynamically switch out the CTA based upon whatever rules they like.</p><p>Multivariate testing (similar to A/B Testing) is also powered by rendering data sources. Typically each option being tested is a different data source on the rendering. A typical red flag on a Sitecore implementation is the lack of use of rendering data sources. A significant amount of out of the box functionality is lost without them.</p><h3>Too Much Content</h3><p>Teams building their first Sitecore implementation often fall into a bit of a trap: everything on the website should be in Sitecore. They&#8217;ve realized the power of Sitecore and want to leverage every bit of that power throughout the implementation. The reality though is that there can be too much data stored in Sitecore. </p><p>Under the hood, all Sitecore items are stored in a single SQL table. That does give you room for many, many items before you run into performance issues but it does put a scalability limit on the content management (CM) environment. A good rule of thumb is to only add items into Sitecore if content authors are expected to edit that content.</p><p>An example of this would be a commerce site that sells car parts. There could potentially be millions of SKUs that would correspond to different Sitecore items. It would be better for that data to live in it&#8217;s own SQL database rather than in Sitecore directly. Often times a single page in Sitecore renders all of those SKUs as a product page. This same approach applies to any content that is controlled by an external system of record. A red flag here would be seeing Sitecore bucket items containing vast numbers of items that all appear to be content editable.</p><h3>Templates and Template Inheritance</h3><p>Templates are the definitions for content items in Sitecore. You might have a CTA template that has fields for <code>Text Message</code>, <code>Image</code>, and <code>Url</code>. This would allow you to make as many CTA items as you like that each have those fields. Template inheritance is where you can make a base template, lets say the one we just defined, and use it to create additional templates.</p><p>An example of this would be to create a Product CTA and a Newsletter CTA. Both would inherit the fields from the Base CTA but also have their own distinct fields:</p><p>Product CTA:</p><ul><li><p>Text Message</p></li><li><p>Image</p></li><li><p>Url</p></li><li><p>Product Name</p></li><li><p>Short Product Description</p></li></ul><p>Newsletter CTA:</p><ul><li><p>Text Message</p></li><li><p>Image</p></li><li><p>Url</p></li><li><p>Newsletter Byline</p></li></ul><p>You can keep going down the rabbit hole with this and at first glance it seems like a good thing. A common practice in software development is Don&#8217;t Repeat Yourself (DRY) and this seems to fit that perfectly. The downside only comes when you need to reorganize the content. </p><p>Finding where a field comes from becomes a more challenging process.  If a template is only one level deep it isn&#8217;t much of an issue. I have seen inheritance structures going 10 layers deep and that is more of a problem. There is no 100% simple way to track it down.</p><p>Changes to base templates can cause unexpected regressions. With many developers on a project it often happens that base templates get used in more places than the original developer of a feature may have expected. An <code>Author</code> base template could be used on nearly any content type. Any change to the base type would impact any rendering that is expecting the old type. This would typically be the type of thing you would expect to catch in a code review but serialized Sitecore content is much more difficult to review than plain code changes in a pull request.</p><p>Additionally, template fields can share the same name. Many base templates will have fields named something like <code>Display Name</code>. Now imagine that your child template has 3 base templates all with the same field name. Where is the data coming from? It becomes a bit of a challenge to find out. </p><p>Generally speaking I would say that any implementation that uses template inheritance more than 1 level deep to be a red flag and likely technical debt that will come back to bite you someday.</p><h2>Solution Architecture</h2><h3>Helix</h3><p>Helix is the recommended architecture for more traditional Sitecore MVC or SXA solutions that would typically be hosted on-prem or platform as a service (PaaS). It provides structural guidelines for the files in the solution, best practices for dependency management, and guidelines for the information architecture. Generally speaking an implementation should follow Helix principles provided that it isn&#8217;t headless, XM Cloud, or following a composable DXP strategy.</p><p>A solution is typically broken down into three layers: Project, Feature, and Foundation. Typically a Helix solution will break the source tree down into the same structure:</p><ul><li><p>Src</p><ul><li><p>Project</p></li><li><p>Feature</p></li><li><p>Foundation</p></li></ul></li></ul><p>These three layers also tie directly into dependency management. The project layer only contains configuration, styling, layouts, and the composition of features. That means that a Project layer can only reference Feature and Foundation projects, never other Projects.</p><p>Features contain the business logic and reusable functionality that is independent of a Project. This is typically for functionality like navigation, search, banners, or call to actions. Features can reference Foundation projects but never other features or projects.</p><p>The Foundation layer generally contains the under the hood functionality shared across features and projects. This covers things like logging, service abstractions, data access, or any other broad set of functionality that can be used by many features or projects.</p><p>Generally speaking it&#8217;s a red flag to see features referencing other features or projects referencing other projects. </p><h3>Configuration Transforms</h3><p>A Sitecore application and all of its underlying functionality are defined via XML configuration files. Every implementation will involve updating the configuration to support new functionality. This is everything from implementing a 404 page (updating the item not found processor) to custom fields in the Solr indexes.</p><p>Remember that a Sitecore application is deployed by dropping the newly built code over top of an existing installation. When reviewing a legacy codebase it&#8217;s quite often that the updated configuration files get added to source control and replace the originals when deployed. This approach, while common, is technical debt.</p><p>A better approach is to use configuration transform files for any change that needs to be applied to the configuration. Typically these files would be added to each Feature or Foundation project that needs it. At application startup, Sitecore picks up these transforms and builds out the final configuration that is used.  You can view the merged configuration of a Sitecore installation by navigating to:</p><p><code>https://localhost.cm/sitecore/admin/showconfig.aspx</code></p><p>It is a huge red flag if a solution doesn&#8217;t use configuration transforms. That typically means that a much deeper audit of the solution is necessary to uncover all of the technical debt.</p><h3>Code Generation</h3><p>By default, Sitecore&#8217;s data access library is not strongly typed and has no typed fields for any of the custom fields that you&#8217;ve created in the content tree (AKA the information architecture). Older Sitecore solutions would typically use a separately purchased product called Team Development for Sitecore (TDS) to synchronize changes from the Sitecore content tree, typically structural content and templates, into source control.</p><p>Virtually every implementation that uses TDS also uses a feature of TDS that generates strongly typed classes for every template that has been synchronized into the solution. While on one hand that&#8217;s a great thing as it gives developers strongly typed objects to use and greatly reduces the occurrence of runtime errors for missing template fields. On the other hand it generates a class for every template that has every field in that class. This often leads to very large models being passed throughout the application when really only a few fields are needed for any given instance.</p><p>It has become a better practice to manually create the classes as they are needed rather than creating every possibility as part of a code generation process. I wouldn&#8217;t necessarily call it a red flag to see code generation in use, but I would say that it&#8217;s a good sign to keep an eye open for other deprecated practices that may still be in use.</p><h3>Glass Mapper</h3><p>Since the out of the box data access library is rather simplistic, nearly every classic Sitecore implementation uses a 3rd party library called Glass Mapper. It&#8217;s essentially a replacement that can map Sitecore items directly to your custom c# classes. It also supports lazy loading for fields that refer to other items and has it&#8217;s own caching layer.</p><p>While it would be fine for a relatively small site to use the out of the box Sitecore data access library, it would be very rare for a site with any complexity to use it.  As Glass Mapper is the only alternative that is actively maintained, it would be a red flag if a complex site wasn&#8217;t using it.</p><h2>Hotfixes</h2><p>One common theme that I&#8217;ve seen in most Sitecore solutions is the failure to apply hotfixes in development environments. It&#8217;s critical that the underlying Sitecore code in development matches that of the higher level environments. Too often the hotfixes from Sitecore get passed off to a managed services team that only apply them to production environments. Hotfixes can and have introduced changes to overall site performance when run at scale. They can also potentially overwrite customizations that have been built.</p><p>A better approach is to include hotfixes as part of the development process. That can be a pull request into a hotfix branch that also gets merged back into the development branch. It won&#8217;t slow down the deployment process and may in fact save time by having more eyes on the changes before they're deployed. </p><h2>Sitecore and the future</h2><p>At the time of this writing, the future is looking fairly strong that Sitecore XM Cloud is going to be the new paradigm for Sitecore implementations. To begin future proofing your implementation you will need to consider that XM Cloud is headless, serialization has standardized on Sitecore Serialization, and the lack of official support for backend customization.</p><p>XM Cloud sites are headless and built very similar to a Sitecore Jss site. That means all of an implementations razor views will need to be converted into react components. Starting to build react components now can save time during a future migration.</p><p>Another challenge is the change to item serialization. The oldest projects typically still use TDS for serialization while newer projects use Unicorn. In either case, the serialized items will need to be replaced with items serialized with Sitecore&#8217;s own serialization tools.</p><p>Customizations are one of the biggest pain points when moving forward toward XM Cloud. Common pipeline modifications like <code>HttpRequestBegin</code> need to be replaced as the frontend is now decoupled from the backend. Other common issues are with dynamic placeholders and url rerouting.  </p><p>Changes to the backend of Sitecore like custom field types and replacements for the rich text editor will also be problematic. XM Cloud has introduced a replacement to the Content Editor called Pages that has very limited support compared to the Content Editor. A good rule of thumb is that any customization that could be broken by Sitecore deploying an update to your implementation should be avoided.</p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[XM Cloud React Components and Fields]]></title><description><![CDATA[When building React components for Sitecore&#8217;s new XM Cloud platform it may not be obvious what the underlying types are.]]></description><link>https://www.corey-mcclelland.com/p/xm-cloud-react-components-and-fields</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/xm-cloud-react-components-and-fields</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Tue, 09 Jan 2024 22:56:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When building React components for Sitecore&#8217;s new XM Cloud platform it may not be obvious what the underlying types are.  I&#8217;m adding them here for future reference:</p><p>Field Types from Sitecore:</p><ul><li><p>ImageField</p></li><li><p>LinkField</p></li><li><p>TextField</p></li><li><p>DateField</p></li><li><p>FileField</p></li><li><p>RichTextField</p></li></ul><p>React Components:</p><ul><li><p>Image</p></li><li><p>Link</p></li><li><p>Text</p></li><li><p>Date</p></li><li><p>File</p></li><li><p>RichText</p></li><li><p>RichTextField</p></li></ul><p></p><p>Reference:</p><p><a href="https://www.npmjs.com/package/@sitecore-jss/sitecore-jss-nextjs?activeTab=code">https://www.npmjs.com/package/@sitecore-jss/sitecore-jss-nextjs?activeTab=code</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[XM Cloud Renderings - Component GraphQL Query]]></title><description><![CDATA[So you have Sitecore XM Cloud up and running locally and you want to make a new component.]]></description><link>https://www.corey-mcclelland.com/p/xm-cloud-renderings-component-graphql</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/xm-cloud-renderings-component-graphql</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Wed, 20 Dec 2023 16:36:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So you have Sitecore XM Cloud up and running locally and you want to make a new component.  Lets say something simple like a links component or a social media component.  The easiest way is to duplicate one of the out of the box components.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o5DR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o5DR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 424w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 848w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 1272w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o5DR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png" width="200" height="288" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:288,&quot;width&quot;:200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:23154,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o5DR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 424w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 848w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 1272w, https://substackcdn.com/image/fetch/$s_!o5DR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff59f9b11-3c33-4b1e-b8aa-fc2fae802625_200x288.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">out of the box components</figcaption></figure></div><p>Lets take a look at the <code>LinkList.tsx</code> component as an example. Make a copy of the file and name it <code>IconLinkList.tsx</code>.  It&#8217;s a fairly typical Sitecore component with the following template definitions:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bE6e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bE6e!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 424w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 848w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 1272w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bE6e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png" width="141" height="74" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:74,&quot;width&quot;:141,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3396,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bE6e!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 424w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 848w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 1272w, https://substackcdn.com/image/fetch/$s_!bE6e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5486a1b-8811-4bc1-87da-2e39199eec78_141x74.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>The <code>Link List Folder</code> contains the <code>Link List Root</code> which then contains the <code>Link(s)</code>.  Lets see if we can update the <code>Link</code> to support an icon as well as the link itself.</p><p>Just like in a traditional Sitecore implementation the first step is to add the field to the template.  In this case I&#8217;ve added the <code>Icon Name</code> field as a text field.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nv-4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nv-4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 424w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 848w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 1272w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nv-4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png" width="810" height="180" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:180,&quot;width&quot;:810,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:10120,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nv-4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 424w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 848w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 1272w, https://substackcdn.com/image/fetch/$s_!nv-4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb79d613a-a224-4168-9a2f-c3121af06fd7_810x180.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>I&#8217;ve also updated the content item to fill in the new icon name.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yV7K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yV7K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 424w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 848w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 1272w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yV7K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png" width="119" height="81" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:81,&quot;width&quot;:119,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2562,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yV7K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 424w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 848w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 1272w, https://substackcdn.com/image/fetch/$s_!yV7K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51fe342f-881b-4fe5-911d-d5392762ac99_119x81.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>As you look through component&#8217;s tsx file you&#8217;ll notice that most of the file is just setting up types and interfaces.  It&#8217;s basically just defining what the data is going to look like for the component.  </p><p>The entrypoint of the component starts here:</p><pre><code><code>export const Default = (props: LinkListProps): JSX.Element =&gt; {
  const datasource = props.fields?.data?.datasource;
  const styles = `component link-list ${props.params.styles}`.trimEnd();
  const id = props.params.RenderingIdentifier;</code></code></pre><p>You can see that the datasource of the component is being passed in as its props and that the props are typed earlier in the code. Now lets say you want to add another field to the data source.  It should be easy right? That&#8217;s where things get interesting. The types defined in this file need to match the data coming from Sitecore.</p><p>So how does that work exactly?  It&#8217;s not like it&#8217;s one item to one datasource.  There&#8217;s a hierarchy of items in that data source.  It starts with the Title text field on the parent item.  All of the links are children of that parent item.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ouDa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ouDa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 424w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 848w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 1272w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ouDa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png" width="180" height="155" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:155,&quot;width&quot;:180,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5631,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ouDa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 424w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 848w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 1272w, https://substackcdn.com/image/fetch/$s_!ouDa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c8341bb-c550-49b8-9d9a-52d0d2a146cd_180x155.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The glue that puts that datasource together is in the rendering.  The rendering contains a field called <code>Component GraphQL Query</code>. It contains a query that pulls data from the root item with the Title text field and all of the links.</p><pre><code>query TitleQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children {
      results {
        field(name: "Link") {
          link: jsonValue
        }
      }
    }
    field(name: "Title") {
      title: jsonValue
    }
  }
}
</code></pre><p>So this is where the trickiness begins.  My first thought was to just add a new duplicate field to the results object above with the new <code>Icon Name</code> field and call it a day as everything just worked.  It failed spectacularly.</p><p>At first glance everything looks fairly decoupled but in reality these existing components are tightly coupled.  The biggest constraints are getting the shape of the output object to match the type defintions in the component.  Those definitions then need to be compatible with the Jss components as well.  In the end I needed to restructure the query a bit so that it looked like this:</p><pre><code>query TitleQuery($datasource: String!, $language: String!) {
  datasource: item(path: $datasource, language: $language) {
    children {
      results {
        field: field(name: "Link") { link: jsonValue}
        iconName: field(name: "Icon Name") { value: value}
      }
    }
    field: field(name: "Title") { title: jsonValue }
  }
}</code></pre><p>The type definitions in the component ended up changing to this:</p><pre><code>type ResultsFieldLink = {
  field: {
    link: LinkField;
  };
  iconName: TextField;
};

type FooterSocialMediaItemProps = {
  key: string;
  index: number;
  total: number;
  field: LinkField;
  iconName: TextField;
};</code></pre><p>With that everything works as expected.</p><p>References:</p><p><a href="https://davidgregories.com/2023/12/12/component-graphql-query-field-in-json-rendering/">Component GraphQL Query Field in Json Rendering</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.corey-mcclelland.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Corey&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Reproduction Steps for Challenging Errors]]></title><description><![CDATA[Have you ever run into hard to reproduce errors in a production environment?]]></description><link>https://www.corey-mcclelland.com/p/reproduction-steps-for-challenging-errors</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/reproduction-steps-for-challenging-errors</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Wed, 23 Oct 2019 12:34:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever run into hard to reproduce errors in a production environment? You can see an error happened in the logs but it's just not reproducible in a development or staging environment? Wouldn't it be nice if you could see what the user was doing before the error happened?</p><p>With Sitecore you can do this easily. Use Sitecore's built in tracker to append page visit history, browser info, etc to your exception messages to give yourself reproduction steps.</p><p>Here's some sample code to get started:</p><pre><code>public string GetUserHistory()
&#9;&#9;{
&#9;&#9;&#9;var pageHistory = string.Empty;
&#9;&#9;&#9;try
&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;var pages = Sitecore.Analytics.Tracker.Current.Session.Interaction.GetPages();
&#9;&#9;&#9;&#9;foreach(var page in pages)
&#9;&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;&#9;pageHistory += $"   Url Visit Date: { page.DateTime.ToString("MM/dd/yyyy h:mm tt") } Url: { page.Url + System.Environment.NewLine }";
&#9;&#9;&#9;&#9;}
&#9;&#9;&#9;}
&#9;&#9;&#9;catch (Exception e)
&#9;&#9;&#9;{
                // the tracker may not have been started
&#9;&#9;&#9;&#9;Sitecore.Diagnostics.Log.Warn("Unable to append analytics data to exception", e, this);
&#9;&#9;&#9;}

&#9;&#9;&#9;return pageHistory;
&#9;&#9;}
</code></pre>]]></content:encoded></item><item><title><![CDATA[Finding Changed TDS Content Items in Source Control]]></title><description><![CDATA[Have you ever run into a situation where you needed to find all of the changed content items committed to git since the last production deployment?]]></description><link>https://www.corey-mcclelland.com/p/finding-changed-content-items-in-source-control</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/finding-changed-content-items-in-source-control</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Thu, 08 Aug 2019 13:47:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/92f3b9c1-9f22-44fd-b4aa-366765b0bc30_1167x340.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever run into a situation where you needed to find all of the changed content items committed to git since the last production deployment? It's easier than you might think. It doesn't even involve nagging all of the other developers on Slack.</p><p>The Git command line gives you great power in finding what's changed. In this case we need to:</p><ol><li><p>Find out when the last release branch/tag was cut</p></li><li><p>Query the log to find any content item that has changed since that date</p></li></ol><p>Let's start by finding the last commit of the previous release. We tag our releases so we can just type <code>git describe --tags</code>. That will display something like <code>CDPROD-20190702-2200-2.0.204-544-g1b6ee7a90</code>. From there we can run the command <code>git log -1 --format=%ai CDPROD-20190702-2200-2.0.204-544-g1b6ee7a90</code> which will give us the date: <code>2019-07-02 15:05:17 -0400</code>.</p><p>Now that we have the date we can query git to find any content items that have changed since then. This command makes the assumption that TDS content items have 'Content' in the path.</p><p><code>git log master --since "2019-07-02T15:05:17-04:00" --name-status --oneline -- '*.Content*.item'</code></p><p>This will give output similar to this:</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JWY-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JWY-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 424w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 848w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 1272w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JWY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;GitLogOutput&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="GitLogOutput" title="GitLogOutput" srcset="https://substackcdn.com/image/fetch/$s_!JWY-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 424w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 848w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 1272w, https://substackcdn.com/image/fetch/$s_!JWY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F078f7f53-b7ec-42d2-af7a-3a93fdbb0c85_1167x340.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a>]]></content:encoded></item><item><title><![CDATA[Performance Issues with Federated Authentication and Virtual Users in Sitecore]]></title><description><![CDATA[Sitecore's Federated Authentication functionality works great and only needs a small amount of code and configuration to get up and running.]]></description><link>https://www.corey-mcclelland.com/p/performance-issues-with-federated-authentication</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/performance-issues-with-federated-authentication</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Mon, 24 Jun 2019 14:55:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sitecore's Federated Authentication functionality works great and only needs a small amount of code and configuration to get up and running. During a recent upgrade to 9.0 Update 2 we ran into an interesting issue where Sitecore was reading and writing huge amounts of data to the <code>ClientData</code> table.</p><p>It turns out that Sitecore never expected both Federated Authentication and Virtual Users to be used at the same time. Typically Federated Authentication would handle everything for you but in our case we had a single sign on (SSO) implementation that wasn't quite compatible that drove use to create virtual users as part of the SSO process.</p><p>Sitecore Support created a patch for us that in their words:</p><pre><code>Fixes excessive ClientData rewrites caused by LoginVirtualUser method when Federated Authentication is enabled.
</code></pre><p>The site is now running great with the patch in place.</p>]]></content:encoded></item><item><title><![CDATA[Sitecore 9: Mongo Migration Tips]]></title><description><![CDATA[Here's a few tips that will help you survive a large mongodb migration into SQL Server.]]></description><link>https://www.corey-mcclelland.com/p/untitled</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/untitled</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Mon, 20 May 2019 23:27:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Here's a few tips that will help you survive a large mongodb migration into SQL Server.</p><h2>Indexes on the SQL Server</h2><p>I highly recommend creating some indexes on the SQL databases that will help increase the the speed of the migration process.</p><p>Indexes for the Verfication database:</p><pre><code>create index VerificationLog_TargetType on VerificationLog (TargetType)
create index VerificationLog_Status on VerificationLog (Status)
create index VerificationLog_OperationType on VerificationLog (OperationType)
create index VerificationLog_Source_Target on VerificationLog(SourceIdentifier, TargetIdentifier)
</code></pre><p>Indexes for the shards:</p><pre><code>create index ContactIdentifiers_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard0].xdb_collection.ContactIdentifiers (ContactId)
create index ContactIdentifiers_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard1].xdb_collection.ContactIdentifiers (ContactId)
create index ContactIdentifiers_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard2].xdb_collection.ContactIdentifiers (ContactId)
create index Contacts_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard0].xdb_collection.Contacts (ContactId)
create index Contacts_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard1].xdb_collection.Contacts (ContactId)
create index Contacts_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard2].xdb_collection.Contacts (ContactId)
create index ContactIdentifiersIndex_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard0].xdb_collection.ContactIdentifiersIndex (ContactId)
create index ContactIdentifiersIndex_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard1].xdb_collection.ContactIdentifiersIndex (ContactId)
create index ContactIdentifiersIndex_ContactId on [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard2].xdb_collection.ContactIdentifiersIndex (ContactId)
</code></pre><p>Here's a query to see the results of a migration. It's useful to compare these numbers to those in MongoDb.</p><pre><code>select b.[Started] as BatchStartDate, 
&#9;format(count(vl.id), 'N0') as RecordCount,
&#9;format(sum(case when vl.TargetType = 'Contact' then 1 else 0 end), 'N0') as ContactCount,
&#9;format(sum(case when vl.TargetType = 'DeviceProfile' then 1 else 0 end), 'N0') as DeviceProfileCount,
&#9;format(sum(case when vl.TargetType = 'Interaction' then 1 else 0 end), 'N0') as InteractionCount
&#9;from [Sitecore.DataExchange.Verification].dbo.VerificationLog vl
&#9;left join [Sitecore.DataExchange.Verification].dbo.[Batches] b on vl.BatchId = b.id
&#9;group by b.[Started]
</code></pre><p>Here's a query that counts the contacts in the shards themselves. It's a nice sanity check against the verification database:</p><pre><code>select format(sum(records), 'N0') as TotalContactsInShards from (
select count(*) as records
&#9;from [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard0].xdb_collection.Contacts s0
union all
select count(*) as records
&#9;from [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard1].xdb_collection.Contacts s1
union all
select count(*) as records
&#9;from [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard2].xdb_collection.Contacts s2
) shards
</code></pre><p>If you need to clear out SQL Server so that you can re-run the migration process, use this script:</p><pre><code>use [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard0]
go
DELETE FROM [xdb_collection].ContactIdentifiers
DELETE FROM [xdb_collection].ContactFacets
DELETE FROM [xdb_collection].InteractionFacets
DELETE FROM [xdb_collection].Interactions
DELETE FROM [xdb_collection].ContactIdentifiersIndex
DELETE FROM [xdb_collection].DeviceProfileFacets
DELETE FROM [xdb_collection].DeviceProfiles
DELETE FROM [xdb_collection].Contacts
go
use [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard1]
go
DELETE FROM [xdb_collection].ContactIdentifiers
DELETE FROM [xdb_collection].ContactFacets
DELETE FROM [xdb_collection].InteractionFacets
DELETE FROM [xdb_collection].Interactions
DELETE FROM [xdb_collection].ContactIdentifiersIndex
DELETE FROM [xdb_collection].DeviceProfileFacets
DELETE FROM [xdb_collection].DeviceProfiles
DELETE FROM [xdb_collection].Contacts
go
use [Sitecore.Xdb.Collection.Database.Sql.ShardMapManagerDb.Shard2]
go
DELETE FROM [xdb_collection].ContactIdentifiers
DELETE FROM [xdb_collection].ContactFacets
DELETE FROM [xdb_collection].InteractionFacets
DELETE FROM [xdb_collection].Interactions
DELETE FROM [xdb_collection].ContactIdentifiersIndex
DELETE FROM [xdb_collection].DeviceProfileFacets
DELETE FROM [xdb_collection].DeviceProfiles
DELETE FROM [xdb_collection].Contacts
go
</code></pre>]]></content:encoded></item><item><title><![CDATA[Federated Authentication in Sitecore 9]]></title><description><![CDATA[One of the great new features of Sitecore 9 is the new federated authentication system.]]></description><link>https://www.corey-mcclelland.com/p/federated-authentication-in-sitecore-9</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/federated-authentication-in-sitecore-9</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Fri, 26 Apr 2019 15:18:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the great new features of Sitecore 9 is the new federated authentication system. You can plug in pretty much any OpenID provider with minimal code and configuration. In this blog I'll go over how to configure a sample OpenID Connect provider.</p><h1>Configuration</h1><p>There's a few different types of configuration that need to be done to get up and running. This takes a few web.config changes, a few app_config changes, and your own custom configurations.</p><h2>Web.config Changes</h2><p>First up is disabling forms authentication. Do this by changing the authentication mode to none:</p><p><code>&lt;authentication mode="None" /&gt;</code></p><p>Next up you need to remove the forms authentication module:</p><pre><code>...
&lt;system.webServer&gt;
&lt;modules&gt;
&lt;remove name="FormsAuthentication" /&gt;
...
</code></pre><h2>App_Config Changes</h2><p>The app config changes need some boilerplate Sitecore configuration as well as your custom configuration for your authentication provider. Sitecore's boilderplate config can be found here: <code>\App_Config\Include\Examples\Sitecore.Owin.Authentication.Enabler.config.example</code>. Basically it just turns on federated authentication and enables a few services in Sitecore.</p><pre><code>&lt;configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/"&gt;
  &lt;sitecore role:require="Standalone or ContentDelivery or ContentManagement"&gt;

    &lt;settings&gt;
      &lt;setting name="FederatedAuthentication.Enabled"&gt;
        &lt;patch:attribute name="value"&gt;true&lt;/patch:attribute&gt;
      &lt;/setting&gt;
    &lt;/settings&gt;

    &lt;services&gt;
      &lt;register serviceType="Sitecore.Abstractions.BaseAuthenticationManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Security.AuthenticationManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" /&gt;
      &lt;register serviceType="Sitecore.Abstractions.BaseTicketManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Security.TicketManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" /&gt;
      &lt;register serviceType="Sitecore.Abstractions.BasePreviewManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Publishing.PreviewManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" /&gt;
    &lt;/services&gt;
</code></pre><p>Next up we need to define our provider:</p><pre><code>&lt;identityProviders hint="list:AddIdentityProvider"&gt;
        &lt;identityProvider id="Crn" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication"&gt;
          &lt;param desc="name"&gt;$(id)&lt;/param&gt;
          &lt;param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" /&gt;
          &lt;caption&gt;Login Button Text&lt;/caption&gt;
          &lt;icon&gt;/sitecore/shell/themes/standard/Images/24x24/IconThatsOnTheButton&lt;/icon&gt;
          &lt;domain&gt;sitecore&lt;/domain&gt; &lt;!-- careful here - you may want to use extranet for public sites --&gt;
          &lt;!--list of identity transfromations which are applied to the provider when a user signin--&gt;
          &lt;transformations hint="list:AddTransformation"&gt;
            &lt;transformation name="Idp Claim" ref="federatedAuthentication/sharedTransformations/setIdpClaim" /&gt;
            &lt;transformation name="Name Identifier Claim" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication"&gt;
              &lt;sources hint="raw:AddSource"&gt;
                &lt;claim name="sub" /&gt;
              &lt;/sources&gt;
              &lt;targets hint="raw:AddTarget"&gt;
                &lt;claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" /&gt;
              &lt;/targets&gt;
              &lt;keepSource&gt;true&lt;/keepSource&gt;
            &lt;/transformation&gt;
          &lt;/transformations&gt;
        &lt;/identityProvider&gt;
      &lt;/identityProviders&gt;
      &lt;sharedTransformations&gt;
      &lt;/sharedTransformations&gt;
</code></pre><p>The transformations can be a bit tricky and can really depend on the environment. If the <code>Idp</code> claim isn't returned by your provider you will need to add it here. It's basically just the name of the provider. Sitecore provides a transform to do this:</p><pre><code>&lt;transformation name="Idp Claim" ref="federatedAuthentication/sharedTransformations/setIdpClaim" /&gt;
</code></pre><p>The other gotcha is the <code>nameidentifier</code> claim is required by Sitecore. If it doesn't exist you will need to create it. Typically this means filling it with data from another claim:</p><pre><code>&lt;transformation name="Name Identifier Claim" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication"&gt;
              &lt;sources hint="raw:AddSource"&gt;
                &lt;claim name="sub" /&gt;
              &lt;/sources&gt;
              &lt;targets hint="raw:AddTarget"&gt;
                &lt;claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" /&gt;
              &lt;/targets&gt;
              &lt;keepSource&gt;true&lt;/keepSource&gt;
            &lt;/transformation&gt;
</code></pre><p>Now we need to tell Sitecore what sites it should use the provider for. In this example we're saying use it on every site but that's almost never what you want. You would typically have two entries here, one for the Content Management (Sitecore) login and a separate one for the public facing sites. Think something like Okta Verify for the content editors and Facebook login for the public site.</p><pre><code>&lt;federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication"&gt;
      &lt;identityProvidersPerSites hint="list:AddIdentityProvidersPerSites"&gt;
        &lt;mapEntry name="Crn" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication"&gt;
          &lt;sites hint="list"&gt;
            &lt;site&gt;shell&lt;/site&gt;
            &lt;site&gt;login&lt;/site&gt;
            &lt;site&gt;admin&lt;/site&gt;
            &lt;site&gt;service&lt;/site&gt;
            &lt;site&gt;modules_shell&lt;/site&gt;
            &lt;site&gt;modules_website&lt;/site&gt;
            &lt;site&gt;website&lt;/site&gt;
            &lt;site&gt;scheduler&lt;/site&gt;
            &lt;site&gt;system&lt;/site&gt;
            &lt;site&gt;publisher&lt;/site&gt;
          &lt;/sites&gt;
          &lt;identityProviders hint="list:AddIdentityProvider"&gt;
            &lt;identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='Crn']" /&gt;
          &lt;/identityProviders&gt;
          &lt;externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication"&gt;
            &lt;param desc="isPersistentUser"&gt;false&lt;/param&gt;
          &lt;/externalUserBuilder&gt;
        &lt;/mapEntry&gt;
      &lt;/identityProvidersPerSites&gt;
</code></pre><p>The tricky part here is the <code>isPersistentUser</code> setting. Persistent users are basically shadow users that are created and visible in Sitecore's security. You can't actually change their info or reset their passwords though. A big downside here is that you're storing personal data like email addresses in Sitecore itself now. This can cause issues if your organization has requirements around how PII (personally identifiable information) is stored. Often times PII needs to be encrypted in transit and at rest. That would require upgrading to SQL Enterprise rather than just using SQL Standard.</p><p>If the setting is false then you don't need to worry about shadow users but you may run into issues with tracking anonymous users across sessions. The documentation isn't 100% clear on this but that's what I've heard. It may take some custom business logic to maintain that tracking.</p><p>The last part of the app_config is registering your pipeline:</p><pre><code>&lt;pipelines&gt;
      &lt;owin.identityProviders&gt;
        &lt;!-- Processors for coniguring providers. Each provider must have its own processor--&gt;
        &lt;processor type="Site.Foundation.LoginProvider.Pipelines.IdentityProviders.CrnIdentityProvider, Site.Foundation.LoginProvider" resolve="true" /&gt;
      &lt;/owin.identityProviders&gt;
    &lt;/pipelines&gt;
</code></pre><h1>Pipeline Setup</h1><p>Here's an example pipeline processor:</p><pre><code>

namespace Site.Foundation.LoginProvider.Pipelines.IdentityProviders
{
    public class CrnIdentityProvider : IdentityProvidersProcessor
    {
        
        public CrnIdentityProvider(
            FederatedAuthenticationConfiguration federatedAuthenticationConfiguration) : base(federatedAuthenticationConfiguration)
        {
            
        }

        protected override string IdentityProviderName
        {
            get { return "Crn"; }
        }

        protected override void ProcessCore(IdentityProvidersArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            IdentityProvider identityProvider = this.GetIdentityProvider();
            string authenticationType = this.GetAuthenticationType();
            var scope = string.Join(" ", Settings.Default.Scope.Cast&lt;string&gt;()) + " openid";

            args.App.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                Authority = Settings.Default.EndpointUri.ToString(),
                ClientId = Settings.Default.ClientId, 
                ClientSecret = Properties.Credentials.Default.ClientSecret, 
                Scope = scope, 
                RedirectUri = Properties.Settings.Default.RedirectUri,
                AuthenticationType = authenticationType,
                ResponseType = "code id_token token",
                SignInAsAuthenticationType = authenticationType,
                UseTokenLifetime = false,
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    SecurityTokenValidated = notification =&gt;
                    {
                        // the user has been validated but we need to call another 
                        // api to get more fields (name, email address, etc) to inject
                        // as claims before the user is created in Sitecore.
                        var accessToken = notification.ProtocolMessage.AccessToken;
                        // call the api with the access token here

                        // apply any transformations from the app_config
                        // this adds the idp claim that's required by Sitecore
                        notification.AuthenticationTicket.Identity
                          .ApplyClaimsTransformations(new TransformationContext(this.FederatedAuthenticationConfiguration, identityProvider));
                        
                        return Task.CompletedTask;
                    },
                },
            });
            
        }
    }
}
</code></pre><p>It should be pretty straightforward but the main gotchas here are more around OpenID Connect then Sitecore. The <code>Authority</code> is the url to authenticate against. The <code>ClientID</code> and <code>ClientSecret</code> are similar to a username and password. The <code>ResponseType</code> is a bit tricky though. If you need to make an API call to add aditional claims before Sitecore creates the user then you will need to make sure that it contains the <code>token</code> value. Otherwise the <code>notification.ProtocolMessage.AccessToken</code> field will be null.</p><h2>Things you can't do in this Pipeline</h2><p>What you see above is pretty much all you can do here. If you want to change cookie names or providers you will need to override another Sitecore pipeline processor. I'd suggest starting with this and see if it works before adding more. The errors that you get from problems here are very confusing and not descriptive. Oh, and they typically don't show up in any of the logs either.</p><h1>Signing In vs Signing Out</h1><p>The main trick here is that you have to request the login url from Sitecore and do a POST to it. If your site is set up to login via links like <code>&lt;a href='/account/login'&gt;Log In&lt;/a&gt;</code> then you've got some fixing to do.</p><p>Sample code to get the login url:</p><pre><code>namespace Site.Foundation.LoginProvider.Services
{
    public class LoginService : ILoginService
    {
        private readonly BaseCorePipelineManager _pipelineManager;

        public LoginService(BaseCorePipelineManager pipelineManager)
        {
            _pipelineManager = pipelineManager;
        }
        
        public string GetLoginUrl(string returnUrl)
        {
            var args = new GetSignInUrlInfoArgs("CFACOM", returnUrl);
            GetSignInUrlInfoPipeline.Run(_pipelineManager, args);

            return args.Result.First().Href;
        }
    }
}
</code></pre><p>Your login link will now look something more like this:</p><pre><code>using (Html.BeginForm(null, null, FormMethod.Post, new { action = Model.SignInUrl }))
            {
                &lt;button type="submit"&gt;
                    Login
                &lt;/button&gt;
            }
</code></pre><p>Logging out uses the fairly standard owin method:</p><pre><code>owinContext.Authentication.SignOut(new AuthenticationProperties { RedirectUri = redirectUri });
</code></pre>]]></content:encoded></item><item><title><![CDATA[Dependency Injection Works in Dev but not Production]]></title><description><![CDATA[You may run into a strange error if you're using code similar to Kam's example code for wiring up dependency injection in Sitecore. You will run into a situation where dependency injection is working great on a development machine but suddenly stops working in staging or production.]]></description><link>https://www.corey-mcclelland.com/p/dependency-injection-works-in-dev-but-not-production</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/dependency-injection-works-in-dev-but-not-production</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Thu, 28 Mar 2019 19:27:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You may run into a strange error if you're using code similar to <a href="https://blogagilereactionio.azurewebsites.net/dependency-injection-works-in-dev-but-not-production/'https://kamsar.net/index.php/2016/08/Dependency-Injection-in-Sitecore-8-2/'">Kam's example code for wiring up dependency injection in Sitecore</a>. You will run into a situation where dependency injection is working great on a development machine but suddenly stops working in staging or production.</p><p>As you investigate further with Sitecore's admin tools you'll realize that it's only controllers that have the problem. It turns out that on release builds with the <code>Optimize Code</code> checkbox checked breaks the assembly naming convention used that's used.</p><p>The fix is fairly simple. Replace the original sample code:</p><pre><code>serviceCollection.AddMvcControllersInCurrentAssembly();
</code></pre><p>with this:</p><pre><code>serviceCollection.AddMvcControllers(typeof(ServicesConfigurator).Assembly);
</code></pre><p><a href="https://blogagilereactionio.azurewebsites.net/dependency-injection-works-in-dev-but-not-production/'https://sitecore.stackexchange.com/a/10034'">* Sitecore StackExchange Answer</a></p>]]></content:encoded></item><item><title><![CDATA[Continuous Deployment - Changes in Sitecore 9]]></title><description><![CDATA[While working on some changes to our current continuous deployment process I came across an interesting change in Sitecore's .update package installer.]]></description><link>https://www.corey-mcclelland.com/p/ci-cd-changes-in-sitecore-9</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/ci-cd-changes-in-sitecore-9</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Wed, 20 Mar 2019 17:47:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While working on some changes to our current continuous deployment process I came across an interesting change in Sitecore's <code>.update</code> package installer. Update packages cannot overwrite any config files that contain <code>connectionstring</code> in their path.</p><p>This likely won't affect many CD deployment implementations but if it does you aren't crazy. It really IS preventing updates to some configs :)</p><p>Example code from Sitecore.Update.dll checking to see if a path is 'safe':</p><pre><code>...
if (!this.IsSafeConfigFile(mappedPath))
  {
    collistionBehavior = CollisionBehavior.Skip;
...
</code></pre><p>and here we see that paths containing <code>connectionstring</code> are not safe:</p><pre><code>protected virtual bool IsSafeConfigFile(string mappedPath)
{
  return !mappedPath.ToLowerInvariant().Contains("connectionstring");
}
</code></pre>]]></content:encoded></item><item><title><![CDATA[XConnect: Concurrency token must not be specified for new entities]]></title><description><![CDATA[If you're new to XConnect you may run into this issue when adding interactions to a contact.]]></description><link>https://www.corey-mcclelland.com/p/xconnect</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/xconnect</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Wed, 13 Mar 2019 17:58:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're new to XConnect you may run into this issue when adding interactions to a contact. You'll probably have code similar to this:</p><pre><code>using (Sitecore.XConnect.Client.XConnectClient client =
                Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())
            {
                var outcome = new Sitecore.XConnect.Outcome(definitionId.Guid, DateTime.UtcNow.Date, "USD", monetaryValue);
                var contact = _contactRepository.GetXConnectContactFromTracker();
                var interaction = new Interaction(contact, InteractionInitiator.Brand, channel, "Web");
                interaction.Events.Add(outcome);
                client.AddContact(contact);
                client.AddInteraction(interaction);
                client.Submit();
            }
</code></pre><p>The above code will give this exception:</p><p><code>Concurrency token must not be specified for new entities</code></p><p>To fix the issue just remove the <code>client.AddContact(contact);</code> line. The contact is added via the interaction.</p>]]></content:encoded></item><item><title><![CDATA[Installing Sitecore 9.0 Update 2]]></title><description><![CDATA[With the recent release of Sitecore 9.1, Sitecore also released a new version of the Sitecore Installation Framework 2.0.]]></description><link>https://www.corey-mcclelland.com/p/installing-sitecore-9-0-update-2</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/installing-sitecore-9-0-update-2</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Sat, 26 Jan 2019 17:06:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the recent release of Sitecore 9.1, Sitecore also released a new version of the Sitecore Installation Framework 2.0. They now recommend installing the framework through their official powershell gallery feed.</p><p>This is great but the installation directions default to installing version 2.0 which isn't compatible with Sitecore 9.0. To install the earlier version do the following:</p><ol><li><p>Register the powershell feed<br><code>Register-PSRepository -Name SitecoreGallery -SourceLocation https://sitecore.myget.org/F/sc-powershell/api/v2</code></p></li><li><p>Install the earlier version of the framework<br><code>Install-Module -Name SitecoreInstallFramework -Repository SitecoreGallery -RequiredVersion 1.2.1</code></p></li></ol>]]></content:encoded></item><item><title><![CDATA[Installing SolrCloud for Sitecore 9 Part 2]]></title><description><![CDATA[This article continues the installation of SolrCloud that was started in part 1]]></description><link>https://www.corey-mcclelland.com/p/installing-solrcloud-for-sitecore-9-part-2</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/installing-solrcloud-for-sitecore-9-part-2</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Thu, 28 Jun 2018 15:01:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/def8f5cd-0daa-46e1-a21e-fdff768edb32_414x218.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article continues the installation of SolrCloud that was started in <a href="https://www.agilereaction.io/installing-solrcloud-for-sitecore-9-part-1">part 1</a></p><h1>Install Solr</h1><p>Download Solr version 6.6.1 <a href="http://archive.apache.org/dist/lucene/solr/6.6.1/solr-6.6.1.zip">here</a>. Unzip it and copy the contents to <code>d:\solr-6.6.1</code></p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7DdJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7DdJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 424w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 848w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 1272w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7DdJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37a37643-bfb7-4467-ba18-f43045a34607_414x218.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;8---Solr-file-location&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="8---Solr-file-location" title="8---Solr-file-location" srcset="https://substackcdn.com/image/fetch/$s_!7DdJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 424w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 848w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 1272w, https://substackcdn.com/image/fetch/$s_!7DdJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a37643-bfb7-4467-ba18-f43045a34607_414x218.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><h1>Configure SSL</h1><p>Next up we need to configure Solr to use SSL. An easy way to do this is to use @kamsar's script from his <a href="https://kamsar.net/index.php/2017/10/Quickly-add-SSL-to-Solr/">blog</a>. We will make some minor changes to use a specific DNS name in addition to localhost.</p><p><strong>These steps will be different for production environments as you shouldn't be using a self signed key. Get in touch with whomever manages the certificate authority for your organization.</strong></p><p>Create the SSL directory in <code>d:\solr-6.6.1</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rhg2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rhg2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 424w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 848w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 1272w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rhg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;9---Solr-ssl-directory&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="9---Solr-ssl-directory" title="9---Solr-ssl-directory" srcset="https://substackcdn.com/image/fetch/$s_!rhg2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 424w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 848w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 1272w, https://substackcdn.com/image/fetch/$s_!rhg2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc36ee20-dad1-467d-ae8e-25923e06124f_408x165.png 1456w" sizes="100vw"></picture><div></div></div></a><p>Next create the <code>solr-ssl.ps1</code> script. This is slightly modified from @kamsar's version. We're changing the keytool command to use our actual dns in addition to localhost.</p><pre><code>param(
&#9;[string]$KeystoreFile = 'solr-ssl.keystore.jks',
&#9;[string]$KeystorePassword = 'secret',
&#9;[string]$SolrDomain = 'localhost',
&#9;[switch]$Clobber
)

$ErrorActionPreference = 'Stop'

### PARAM VALIDATION
if($KeystorePassword -ne 'secret') {
&#9;Write-Error 'The keystore password must be "secret", because Solr apparently ignores the parameter'
}

if((Test-Path $KeystoreFile)) {
&#9;if($Clobber) {
&#9;&#9;Write-Host "Removing $KeystoreFile..."
&#9;&#9;Remove-Item $KeystoreFile
&#9;} else {
&#9;&#9;$KeystorePath = Resolve-Path $KeystoreFile
&#9;&#9;Write-Error "Keystore file $KeystorePath already existed. To regenerate it, pass -Clobber."
&#9;}
}

$P12Path = [IO.Path]::ChangeExtension($KeystoreFile, 'p12')
if((Test-Path $P12Path)) {
&#9;if($Clobber) {
&#9;&#9;Write-Host "Removing $P12Path..."
&#9;&#9;Remove-Item $P12Path
&#9;} else {
&#9;&#9;$P12Path = Resolve-Path $P12Path
&#9;&#9;Write-Error "Keystore file $P12Path already existed. To regenerate it, pass -Clobber."
&#9;}
}

try {
&#9;$keytool = (Get-Command 'keytool.exe').Source
} catch {
&#9;$keytool = Read-Host "keytool.exe not on path. Enter path to keytool (found in JRE bin folder)"

&#9;if([string]::IsNullOrEmpty($keytool) -or -not (Test-Path $keytool)) {
&#9;&#9;Write-Error "Keytool path was invalid."
&#9;}
}

Write-Host ''
Write-Host 'Generating JKS keystore...'
&amp; $keytool -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass $KeystorePassword -storepass $KeystorePassword -validity 9999 -keystore $KeystoreFile -ext SAN=DNS:$SolrDomain,DNS:SOLRNODE1.COMPANY.COM,IP:127.0.0.1 -dname "CN=SOLRNODE1, OU=Organizational Unit, O=Organization, L=Location, ST=State, C=Country"

Write-Host ''
Write-Host 'Generating .p12 to import to Windows...'
&amp; $keytool -importkeystore -srckeystore $KeystoreFile -destkeystore $P12Path -srcstoretype jks -deststoretype pkcs12 -srcstorepass $KeystorePassword -deststorepass $KeystorePassword

Write-Host ''
Write-Host 'Trusting generated SSL certificate...'
$secureStringKeystorePassword = ConvertTo-SecureString -String $KeystorePassword -Force -AsPlainText
$root = Import-PfxCertificate -FilePath $P12Path -Password $secureStringKeystorePassword -CertStoreLocation Cert:\LocalMachine\Root
Write-Host 'SSL certificate is now locally trusted. (added as root CA)'

Write-Host ''
Write-Host '########## NEXT STEPS ##########' -ForegroundColor Green
Write-Host ''
Write-Host '1. Copy your keystore to $SOLR_HOME\server\etc (MUST be here)' -ForegroundColor Green

if(-not $KeystoreFile.EndsWith('solr-ssl.keystore.jks')) {
&#9;Write-Warning 'Your keystore file is not named "solr-ssl.keystore.jks"'
&#9;Write-Warning 'Solr requires this exact name, so make sure to rename it before use.'
}

$KeystorePath = Resolve-Path $KeystoreFile
Write-Host ''
Write-Host '2. Add the following lines to your solr.in.cmd:' -ForegroundColor Green
Write-Host ''
Write-Host "set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_KEY_STORE_PASSWORD=$KeystorePassword" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_TRUST_STORE_PASSWORD=$KeystorePassword" -ForegroundColor Yellow
Write-Host ''
Write-Host 'Done!'
</code></pre><p>Copy the generated keystore <code>solr-ssl.keystore.jks</code> to the <code>d:\solr-6.6.1\server\etc</code> directory.</p><p>Next open the <code>d:\solr-6.1.1\bin\solr.in.cmd</code> file an add the following lines to the end:</p><pre><code>set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks
set SOLR_SSL_KEY_STORE_PASSWORD=secret
set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks
set SOLR_SSL_TRUST_STORE_PASSWORD=secret
</code></pre><h1>Configure the Firewall</h1><p>Open TCP port 8983 on the firewall to allow connections to Solr.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qRrA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qRrA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 424w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 848w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 1272w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qRrA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;11---Solr-ports&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="11---Solr-ports" title="11---Solr-ports" srcset="https://substackcdn.com/image/fetch/$s_!qRrA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 424w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 848w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 1272w, https://substackcdn.com/image/fetch/$s_!qRrA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56bb96f6-63aa-4fb8-b291-ce87cb7d8a8b_425x346.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><h1>Initial Testing</h1><p>You can now test Solr in a command prompt by running:<br><code>bin\solr -c -f -z "SOLRNODE1.COMPANY.COM:2181,SOLRNODE2.COMPANY.COM:2181,SOLRNODE3.COMPANY.COM:2181" -m 18g -p 8983</code></p><p>Navigate to <code>https://SOLRNODE1.COMPANY.COM:8983</code> to see solr running:</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NJP5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NJP5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 424w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 848w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 1272w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NJP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;10---web-browser&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="10---web-browser" title="10---web-browser" srcset="https://substackcdn.com/image/fetch/$s_!NJP5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 424w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 848w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 1272w, https://substackcdn.com/image/fetch/$s_!NJP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9bbf710-4123-40d1-9c54-a2b8a4ffdef6_522x312.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><h1>Solr as a Windows Service</h1><p>We need to set Solr to run automatically. We'll use a process that's very similar to what we did with ZooKeeper.</p><p>Create the directory <code>d:\solr-6.6.1\service</code>. Download a copy of <code>prunsrv.exe</code>.</p><p>Create a new bat file called <code>InstallSolrService.bat</code>. Add the following content to it:</p><pre><code>@echo off

set SERVICE_NAME=solr
set SERVICE_HOME=d:\solr-6.6.1
set PR_INSTALL=%SERVICE_HOME%\service\prunsrv.exe

@REM Service Log Configuration
set PR_LOGPREFIX=%SERVICE_NAME%
set PR_LOGPATH=%SERVICE_HOME%\logs
set PR_STDOUTPUT=auto
set PR_STDERROR=auto
set PR_LOGLEVEL=Debug

set PR_STARTUP=auto
set PR_STARTMODE=exe
set PR_STARTIMAGE=%SERVICE_HOME%\bin\solr.cmd
set PR_STARTPARAMS=-c -z "SOLRNODE1.COMPANY.COM:2181,SOLRNODE2.COMPANY.COM:2181,SOLRNODE3.COMPANY.COM:2181" -m 18g -p 8983

@REM Shutdown Configuration
set PR_STOPMODE=exe
set PR_STOPIMAGE=%SERVICE_HOME%\bin\solr.cmd
set PR_STOPPARAMS=stop -p 8983

%PR_INSTALL% //IS/%SERVICE_NAME% ^
   --Description="Solr-6.6.1" ^
   --DisplayName="%SERVICE_NAME%" ^
   --Install="%PR_INSTALL%" ^
   --Startup="%PR_STARTUP%" ^
   --LogPath="%PR_LOGPATH%" ^
   --LogPrefix="%PR_LOGPREFIX%" ^
   --LogLevel="%PR_LOGLEVEL%" ^
   --StdOutput="%PR_STDOUTPUT%" ^
   --StdError="%PR_STDERROR%" ^
   --StartMode="%PR_STARTMODE%" ^
   --StartImage="%PR_STARTIMAGE%" ^
   --StartParams="%PR_STARTPARAMS%" ^
   --StopMode="%PR_STOPMODE%" ^
   --StopImage="%PR_STOPIMAGE%" ^
   --StopParams="%PR_STOPPARAMS%"

if not errorlevel 1 goto installed
echo Failed to install "%SERVICE_NAME%" service. Refer to log in %PR_LOGPATH%
exit /B 1

:installed
echo The Service "%SERVICE_NAME%" has been installed
exit /B 0
</code></pre><p>You will need to update the line that contains the DNS addresses and the memory usage.</p><p><code>set PR_STARTPARAMS=-c -z "SOLRNODE1.COMPANY.COM:2181,SOLRNODE2.COMPANY.COM:2181,SOLRNODE3.COMPANY.COM:2181" -m 18g -p 8983</code></p><p>The <code>-m</code> flag is currently setting the memory to 18 GB.</p><p>Run the bat file from a command prompt to install the service. Don't forget to set it to run automatically from <code>services.msc</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KaJu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KaJu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 424w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 848w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 1272w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KaJu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;12---solr-service&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="12---solr-service" title="12---solr-service" srcset="https://substackcdn.com/image/fetch/$s_!KaJu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 424w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 848w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 1272w, https://substackcdn.com/image/fetch/$s_!KaJu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e05634d-8ab3-4624-a0d6-5b301bb303cd_400x359.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><h1>Rinse and Repeat</h1><p>Repeat the install process on the other nodes in the cluster.</p><h1>Using SolrCloud with Sitecore</h1><p>At this point you should add a load balancer that points to the 3 SolrCloud nodes. Point Sitecore to the load balancer and you're good to go.</p>]]></content:encoded></item><item><title><![CDATA[Sitecore 9 Error: "There is no method 'Filter' on type...]]></title><description><![CDATA[While building out a new Sitecore 9 implementation we ran into the following error:]]></description><link>https://www.corey-mcclelland.com/p/sitecore-9-error-there-is-no-method-filter-on-type</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/sitecore-9-error-there-is-no-method-filter-on-type</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Tue, 26 Jun 2018 12:24:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12MS!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb26877a1-25bc-4007-a14a-670e6d5b638e_144x144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While building out a new Sitecore 9 implementation we ran into the following error:</p><pre><code>There is no method 'Filter' on type 'Sitecore.ContentSearch.Linq.QueryableExtensions' that matches the specified arguments]
</code></pre><p>It turns out that this error message is caused when Sitecore starts before the Solr service is started. It does not go away after the Solr server starts either. The solution is to either do an <code>iisreset</code> from a command prompt or add a new configuration transform to your project to prevent the issue going forward.</p><pre><code>&lt;sitecore&gt;
  &lt;pipelines&gt;
    &lt;initialize&gt;
      &lt;processor type="Sitecore.ContentSearch.SolrProvider.Pipelines.Loader.InitializeSolrProvider, Sitecore.ContentSearch.SolrProvider"&gt;
        &lt;patch:delete /&gt;
      &lt;/processor&gt;
      &lt;processor type="Sitecore.ContentSearch.SolrProvider.Pipelines.Loader.InitializeSolrProvider, Sitecore.ContentSearch.SolrProvider" patch:before="*[@type='Sitecore.Pipelines.Loader.InitializeScheduler, Sitecore.Kernel']"/&gt;
    &lt;/initialize&gt;
  &lt;/pipelines&gt;
&lt;/sitecore&gt;
</code></pre><p>This solution is from the <a href="https://sitecore.stackexchange.com/questions/8915/there-is-no-method-getresults-on-type-sitecore-contentsearch-linq-queryableex">Sitecore StackExchange site</a>.</p>]]></content:encoded></item><item><title><![CDATA[Installing SolrCloud for Sitecore 9 Part 1]]></title><description><![CDATA[SolrCloud is the name of the functionality in Solr that supports high availability and fault tolerance through the use of a cluster.]]></description><link>https://www.corey-mcclelland.com/p/installing-solrcloud-for-sitecore-part-1</link><guid isPermaLink="false">https://www.corey-mcclelland.com/p/installing-solrcloud-for-sitecore-part-1</guid><dc:creator><![CDATA[Corey McClelland]]></dc:creator><pubDate>Fri, 22 Jun 2018 14:13:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ab5bb223-07e8-43db-8e82-a3e223eaea2d_353x130.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>SolrCloud is the name of the functionality in Solr that supports high availability and fault tolerance through the use of a cluster. Using SolrCloud takes away a single point of failure that exists in many Sitecore solutions. Our goal is to create a 3 node SolrCloud cluster.</p><h1>ZooKeeper</h1><p>ZooKeeper is the underlying system that maintains the configuration for the SolrCloud environment. It can be thought of as a distributed filesystem that all Solr nodes have aceess to.</p><h2>Installation</h2><p>Let's install ZooKeeper onto our 3 node cluster. It can theoretically be installed on its own cluster but it's more common to put it on the same servers that Solr will use.</p><p>For this installation I'm using ZooKeeper v3.4.9.</p><p><strong>Install the Java Runtime Environment</strong><br>Since both ZooKeeper and Solr are Java apps, we will need to install the JRE. I used v1.8 for this installation as the latest version had some compatibility issues with Solr.</p><p>After the installer completes you will need to set the JAVA_HOME system environment variable. Its value should be: <code>C:\Program Files\Java\jre1.8.0_172</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o0k4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o0k4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 424w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 848w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 1272w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o0k4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;1---JRE-JAVA_HOME&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="1---JRE-JAVA_HOME" title="1---JRE-JAVA_HOME" srcset="https://substackcdn.com/image/fetch/$s_!o0k4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 424w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 848w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 1272w, https://substackcdn.com/image/fetch/$s_!o0k4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd01bb10e-403a-4d0c-aa59-4b4c3cfc3458_353x130.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><p>Next we need to update the Path environment variable to include the JAVA_HOME variable: <code>%JAVA_HOME%\bin;</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Stt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Stt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 424w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 848w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 1272w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Stt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;2---JRE-PATH&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="2---JRE-PATH" title="2---JRE-PATH" srcset="https://substackcdn.com/image/fetch/$s_!8Stt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 424w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 848w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 1272w, https://substackcdn.com/image/fetch/$s_!8Stt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd24f0598-8db0-484c-b9b5-874d8c2643ed_432x235.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><p><strong>Install ZooKeeper</strong><br>Extract the ZooKeeper package to the data drive of your server. In our case it will be located in <code>D:\zookeeper-3.4.9</code>.</p><p>Create a system environment variable for ZooKeeper. It should be called <code>ZOOKEEPER_HOME</code> and have a value of <code>D:\zookeeper-3.4.9</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!co_b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!co_b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 424w, https://substackcdn.com/image/fetch/$s_!co_b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 848w, https://substackcdn.com/image/fetch/$s_!co_b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 1272w, https://substackcdn.com/image/fetch/$s_!co_b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!co_b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;3---Zookeeper-Home&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="3---Zookeeper-Home" title="3---Zookeeper-Home" srcset="https://substackcdn.com/image/fetch/$s_!co_b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 424w, https://substackcdn.com/image/fetch/$s_!co_b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 848w, https://substackcdn.com/image/fetch/$s_!co_b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 1272w, https://substackcdn.com/image/fetch/$s_!co_b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff533cb12-efd0-4e47-80c8-1e10197f6620_395x158.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><p>Update the system path to include the new variable:</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lz8j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lz8j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 424w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 848w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 1272w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lz8j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b779bd68-09e5-4392-ac33-6922fe92627d_449x219.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;4---Zookeeper-PATH&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="4---Zookeeper-PATH" title="4---Zookeeper-PATH" srcset="https://substackcdn.com/image/fetch/$s_!lz8j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 424w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 848w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 1272w, https://substackcdn.com/image/fetch/$s_!lz8j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb779bd68-09e5-4392-ac33-6922fe92627d_449x219.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><p>Note that like the JAVA_HOME we've appended the bin directory to the path.</p><p>Next we need to copy the %ZOOKEEPER_HOME%\conf\zoo_sample.cfg to zoo.cfg. Open the file and change the dataDir variable to point to this path: <code>dataDir=d:\\zookeeper-3.4.9\\data</code>. Make sure this directory exists.</p><p>Create a file called "myid" in the data folder. It must contain the integer id of the server.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2pHV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2pHV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 424w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 848w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 1272w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2pHV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;5-Zookeeper-myid&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="5-Zookeeper-myid" title="5-Zookeeper-myid" srcset="https://substackcdn.com/image/fetch/$s_!2pHV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 424w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 848w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 1272w, https://substackcdn.com/image/fetch/$s_!2pHV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F114ae75d-cbd0-40fe-8399-aaef439cd172_495x187.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><p>Add the DNS entries for the servers in the cluster to the bottom of the zoo.cfg file:</p><pre><code>server.1=SERVER1.COMPANY.COM:2888:3888
server.2=SERVER2.COMPANY.COM:2888:3888
server.3=SERVER3.COMPANY.COM:2888:3888
</code></pre><p>Confirm that this zookeeper node starts correctly by running "zkserver" from the command line.</p><p><strong>Run ZooKeeper as a Service</strong><br>Create the directory <code>%ZOOKEEPER_HOME%\service</code>. Download a copy of <code>prunsrv.exe</code> to this folder. Create a new file called <code>ZooKeeperInstallService.bat</code> with the following contents:</p><pre><code>@echo off

SET HOME=d:\zookeeper-3.4.9
SET SERVICE_DIR=d:\zookeeper-3.4.9\service
SET SERVICE_LOGPATH=d:\zookeeper-3.4.9\logs

REM Service configuration
SET SERVICE_NAME=ZooKeeperService
SET PR_INSTALL=%SERVICE_DIR%\prunsrv.exe
SET PR_PIDFILE=servicepid

REM Service log configuration
SET PR_LOGPREFIX=%SERVICE_NAME%
SET PR_LOGPATH=%SERVICE_LOGPATH%
SET PR_STDOUTPUT=%SERVICE_LOGPATH%\stdout.txt
SET PR_STDERROR=%SERVICE_LOGPATH%\stderr.txt
SET PR_LOGLEVEL=All

REM Zookeeper - Classpath Configuration
SET ZOOCFGDIR=%HOME%\conf
SET ZOO_LOG_DIR=%HOME%
SET ZOO_LOG4J_PROP=INFO,CONSOLE
SET ZOOCFG=%ZOOCFGDIR%\zoo.cfg
SET ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain

SET CLASSPATH=%ZOOCFGDIR%
SET CLASSPATH=%HOME%\zookeeper-3.4.9.jar;%CLASSPATH%
SET CLASSPATH=%HOME%\lib\jline-0.9.94.jar;%CLASSPATH%
SET CLASSPATH=%HOME%\lib\log4j-1.2.16.jar;%CLASSPATH%
SET CLASSPATH=%HOME%\lib\netty-3.10.5.Final.jar;%CLASSPATH%
SET CLASSPATH=%HOME%\lib\slf4j-api-1.6.1.jar;%CLASSPATH%
SET CLASSPATH=%HOME%\lib\slf4j-log4j12-1.6.1.jar;%CLASSPATH%
SET PR_CLASSPATH=%CLASSPATH%

SET PR_JVMOPTIONS="-Dzookeeper.log.dir=%ZOO_LOG_DIR%";"-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%"

REM Startup configuration
SET PR_STARTUP=manual
SET PR_STARTMODE=java
SET PR_STARTCLASS=%ZOOMAIN%
SET PR_STARTPARAMS=%ZOOCFG%
 
REM JVM configuration
SET PR_JVM=auto
SET PR_JVMMS=256
SET PR_JVMMX=1024

REM Service Shutdown configuration
SET PR_STOPMODE=exe
SET PR_STOPIMAGE=%SERVICE_DIR%\ServiceKiller.exe
SET PR_STOPPARAMS=%SERVICE_LOGPATH%\servicepid
 
REM Install service
prunsrv.exe //IS//%SERVICE_NAME%
</code></pre><p>Create the <code>%ZOOKEEPER_HOME%\logs</code> folder if it doesn't already exist.</p><p>Run the bat file and confirm that it created a service entry in services.msc. You will also need to change the startup mode to be automatic.</p><p><strong>Open the Firewall</strong><br>Next up is adding the required ZooKeeper ports to the firewall. These TCP ports need to allow incoming connections: <code>2181, 2888, 3888</code>.</p><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZbQl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZbQl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 424w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 848w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 1272w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZbQl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;7---zookeeper-firewall-ports&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="7---zookeeper-firewall-ports" title="7---zookeeper-firewall-ports" srcset="https://substackcdn.com/image/fetch/$s_!ZbQl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 424w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 848w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 1272w, https://substackcdn.com/image/fetch/$s_!ZbQl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fbb35e1-35e0-4692-9b39-aa1f617eb624_493x389.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><p><strong>Rinse and Repeat</strong><br>Repeat these steps on the other two servers in the cluster. Remember to change the server id in the <code>myid</code> file.</p><p><strong>Install Solr</strong><br>Installing Solr will be covered in part 2.</p>]]></content:encoded></item></channel></rss>