<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Richard Szalay's Blog</title>
    <description></description>
    <link>https://blog.richardszalay.com/</link>
    <atom:link href="https://blog.richardszalay.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 09 Jul 2019 01:26:19 +0000</pubDate>
    <lastBuildDate>Tue, 09 Jul 2019 01:26:19 +0000</lastBuildDate>
    <generator>Jekyll v3.8.5</generator>
    
      <item>
        <title>Sitecore App Service Warm-Up Demystified</title>
        <description>&lt;p&gt;It’s well known the &lt;a href=&quot;https://docs.microsoft.com/en-us/iis/configuration/system.webserver/applicationinitialization/&quot;&gt;Application Initialization&lt;/a&gt; IIS module can assist in “warming up” an application. The problem this feature has always faced (for me, at least) is that its integration with Azure is not particularly well documented, and mostly limited to occasional forum posts that indicate deeper integration with the App Service load balancer without providing the detail.&lt;/p&gt;

&lt;p&gt;That is, until now. Microsoft was kind enough to direct me to &lt;a href=&quot;https://michaelcandido.com/app-service-warm-up-demystified/&quot;&gt;App Service Warm-Up Demystified&lt;/a&gt; by Michael Candido, a Principal Software Engineering Manager on the App Service team. This blog post goes into an incredible amount of detail about how the feature works in App Service. I’m not going to try to reproduce Michael’s blog post here, and I do consider it required reading if using this feature, but I’d like to summarise the feature’s behaviour in areas that directly affect Sitecore.&lt;/p&gt;

&lt;h1 id=&quot;tldr&quot;&gt;tl;dr&lt;/h1&gt;

&lt;p&gt;To reduce/remove any “cold” start delays to end users:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use deployment slots for blue/green deployments&lt;/li&gt;
  &lt;li&gt;Use Application Initialization to warm up a specific resources for scale and maintenance events&lt;/li&gt;
  &lt;li&gt;Use Advanced Application Restart to restart the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things to be aware of when using Application Initialization:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure your warmup calls can actually reach the target Sitecore “site” using specific hostName and/or URL rewrite rules&lt;/li&gt;
  &lt;li&gt;Prevent caching of unintended absolute URLs (eg. “http://localhost”) by using relative URLs or setting the &lt;code class=&quot;highlighter-rouge&quot;&gt;targetHostname&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;scheme&lt;/code&gt; properties on the target site&lt;/li&gt;
  &lt;li&gt;Make sure your application can warm up (including app init URLs) in under 10 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;

&lt;p&gt;Generally speaking, the Application Initialization module simply requests a number of URLs sequentially when the application pool starts. It does so in the background, without interfering with traditional requests.&lt;/p&gt;

&lt;p&gt;Azure App Service, however, provides additional support for this feature by preventing instances from receiving traffic until after all of the initialization URLs have been loaded sequentially.&lt;/p&gt;

&lt;p&gt;This prevention applies in the following scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Adding new instances via scale &lt;em&gt;out&lt;/em&gt; (either manually or autoscale)&lt;/li&gt;
  &lt;li&gt;Swapping deployments slots&lt;/li&gt;
  &lt;li&gt;Instances being taken in and out for maintenance&lt;/li&gt;
  &lt;li&gt;Advanced Application Restart (in “Diagnose and Solve Problems”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; prevent requests from being sent to an instance in the following scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Deployments (use deployment slots)&lt;/li&gt;
  &lt;li&gt;Scaling &lt;em&gt;up&lt;/em&gt; instances (there’s a &lt;a href=&quot;https://feedback.azure.com/forums/169385-web-apps/suggestions/33580975-add-application-initialization-support-for-scale-u&quot;&gt;feature request for this&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Restarting your application via the “Restart” option (use Advanced Application Restart instead)&lt;/li&gt;
  &lt;li&gt;Applications not running in Azure App Service (on-prem, Azure IaaS, etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;sitecore-integration&quot;&gt;Sitecore integration&lt;/h1&gt;

&lt;p&gt;There are a number of things to be aware of when using Application Initialization with Sitecore:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;Any&lt;/em&gt; response from an initialization URL is sufficient, even 302 (Redirect) or 500 (Error)&lt;/li&gt;
  &lt;li&gt;All app initialization requests are sent using “localhost” unless configured otherwise&lt;/li&gt;
  &lt;li&gt;All app initialization requests are sent using HTTP (ie. not HTTPS)&lt;/li&gt;
  &lt;li&gt;The instance will be torn down if the application doesn’t restart in 10 minute (30 in some circumstances)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If not planned for, these nuances can result in instances receiving traffic too early because all of the requests resulted in not-found errors or redirects without actually warming anything up.&lt;/p&gt;

&lt;p&gt;(Once receiving traffic, external requests will time out after 230 seconds and return a 502 status code. If you’re seeing this, it’s likely that your initialization URLs aren’t executing properly.)&lt;/p&gt;

&lt;p&gt;Let’s look at these issues and how you can work around them.&lt;/p&gt;

&lt;h2 id=&quot;localhost&quot;&gt;Localhost&lt;/h2&gt;

&lt;p&gt;Assuming your target site isn’t configured to match &lt;code class=&quot;highlighter-rouge&quot;&gt;localhost&lt;/code&gt;, the request will likely result in a 404 (or a 302 to the Not Found page). There are a number of things to consider when fixing that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Whether to target the site via the host header or override the site via &lt;code class=&quot;highlighter-rouge&quot;&gt;?sc_site&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Whether to configure the target site on each initialization URL, or ‘globally’ via URL Rewrite&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My personal preference is to rewrite &lt;code class=&quot;highlighter-rouge&quot;&gt;?sc_site&lt;/code&gt;, since it’s portable between environments, using either Rewrite rule if there’s only one target site, or on each warmup URL if there are multiple sites being.&lt;/p&gt;

&lt;p&gt;However, since your requirements may necessitate any combination of these options, I’ll provide configuration samples for each.&lt;/p&gt;

&lt;h3 id=&quot;using-applicationinitialization&quot;&gt;Using &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;applicationInitialization&amp;gt;&lt;/code&gt;&lt;/h3&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;applicationInitialization&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;doAppInitAfterRestart=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Via site name --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;initializationPage=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/?sc_site=veryexcellent&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Via host name --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;initializationPage=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hostName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;veryexcellentsite.com&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/applicationInitialization&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using-rules&quot;&gt;Using &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;rules&amp;gt;&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Modifying the host using a rewrite rule requires that you add &lt;code class=&quot;highlighter-rouge&quot;&gt;HTTP_HOST&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;system.webServer/rewrite/allowedServerVariables&lt;/code&gt; via an &lt;a href=&quot;https://github.com/projectkudu/kudu/wiki/Xdt-transform-samples&quot;&gt;applicationHost.xdt transform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember that rules are processed in order, so this will need to be first.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;rules&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Via site name --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;rule&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Change warmup site&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stopProcessing=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;match&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;(.*)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;conditions&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{HTTP_USER_AGENT}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^IIS Application Initialization Warmup$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{REMOTE_ADDR}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^127\.0\.0\.\d+$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/conditions&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;action&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Rewrite&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{R:0}?sc_site=veryexcellent&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;appendQueryString=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Via host name --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;rule&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Change warmup host&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stopProcessing=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;match&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.*&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;conditions&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{HTTP_USER_AGENT}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^IIS Application Initialization Warmup$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{REMOTE_ADDR}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^127\.0\.0\.\d+$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/conditions&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;serverVariables&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;set&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HTTP_HOST&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;veryexcellentsite.com&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/serverVariables&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rules&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;http&quot;&gt;HTTP&lt;/h2&gt;

&lt;p&gt;HTTPS can’t be rewritten via a rule, but if you have an HTTP-&amp;gt;HTTPS redirect you’ll need to inject an earlier rule that allows the warmup request to go through unsecured (the 302 won’t be followed, and will be treated as a success response).&lt;/p&gt;

&lt;p&gt;NOTE: If you’re using a rule to apply the site/host, you can simply set &lt;code class=&quot;highlighter-rouge&quot;&gt;stopProcessing=&quot;true&quot;&lt;/code&gt; on that and this rule won’t be required.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;rule&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No HTTP-&amp;gt;HTTPS&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stopProcessing=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;match&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.*&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;conditions&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{HTTP_USER_AGENT}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^IIS Application Initialization Warmup$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;input=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{REMOTE_ADDR}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^127\.0\.0\.\d+$&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/conditions&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;action&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;None&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;cached-renderings-with-absolute-links&quot;&gt;Cached Renderings with Absolute Links&lt;/h2&gt;

&lt;p&gt;If you have any cached renderings that use absolute links and you use &lt;code class=&quot;highlighter-rouge&quot;&gt;AlwaysIncludeServerUrl&lt;/code&gt; (either in those renderings, or globally), make sure you set the following in your site configuration so that your links aren’t cached as “http://localhost/*”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;targetHostname&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;scheme&lt;/code&gt; (if using https)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;time-limit&quot;&gt;Time limit&lt;/h2&gt;

&lt;p&gt;Finally, the application has 10 minutes to complete warmup before the VM is shut down. This might be as long as 30 minutes, but as it’s not reliable it’s best to aim for 10 minutes.&lt;/p&gt;

&lt;p&gt;Individual warmup requests do not have a timeout applied so you can use them to “wait” for certain background processes to complete, if required.&lt;/p&gt;

&lt;p&gt;I won’t cover startup optimisation as it’s a large topic on its own, but here’s two easy things that can help:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure the site is configured to use Roslyn (&lt;a href=&quot;https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform&quot;&gt;Microsoft.CodeDom.Providers.DotNetCompilerPlatform&lt;/a&gt;) as the cshtml compiler rather than the default. &lt;strong&gt;I’ve seen startup time drop by 3-4 minutes doing this&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Precompile views (for example, using &lt;a href=&quot;https://github.com/RazorGenerator/RazorGenerator/wiki/Using-RazorGenerator.MsBuild&quot;&gt;RazorGenerator.MsBuild&lt;/a&gt;) and &lt;a href=&quot;https://kamsar.net/index.php/2016/09/Precompiled-Views-with-Sitecore-8-2/&quot;&gt;registering the assembly with Sitecore&lt;/a&gt;. Should should drop another 30-120 seconds from the startup time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;debugging-warm-up-requests&quot;&gt;Debugging Warm-Up Requests&lt;/h2&gt;

&lt;p&gt;Warm-up requests sent by the Application Initialization module don’t appear in the IIS logs. They will be logged by Failed Request Tracing, but there’s also no way to filter this logging specifically for warmup requests.&lt;/p&gt;

&lt;p&gt;One strategy is to create a dummy page that you can send ‘marker’ requests to:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;applicationInitialization&amp;gt;
  &amp;lt;add initializationPage=&quot;/Init.aspx?phase=home&quot; /&amp;gt;
  &amp;lt;add initializationPage=&quot;/?sc_site=awesome&quot; /&amp;gt;

  &amp;lt;add initializationPage=&quot;/Init.aspx?phase=landing&quot; /&amp;gt;
  &amp;lt;add initializationPage=&quot;/first?sc_site=awesome&quot; /&amp;gt;
  &amp;lt;add initializationPage=&quot;/second?sc_site=awesome&quot; /&amp;gt;
  &amp;lt;add initializationPage=&quot;/third?sc_site=awesome&quot; /&amp;gt;
&amp;lt;/applicationInitialization&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This allows you to filter Failed Request Tracing to this page and you can review the logs to compare timestamps. If there is little to no time between marker requests, there’s probably errors occuring for the intervening requests.&lt;/p&gt;

&lt;p&gt;You can also identify requests made by/during warmup:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Warmup requests will have a user agent of &lt;code class=&quot;highlighter-rouge&quot;&gt;IIS Application Initialization Warmup&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Regular/external requests made while warmup is in progress will have a &lt;code class=&quot;highlighter-rouge&quot;&gt;APP_WARMING_UP&lt;/code&gt; server variable set to &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Wed, 03 Jul 2019 05:35:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2019/07/03/sitecore-azure-appinit/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2019/07/03/sitecore-azure-appinit/</guid>
        
        <category>sitecore</category>
        
        <category>azure</category>
        
        <category>performance</category>
        
        
      </item>
    
      <item>
        <title>Easily debug Sitecore assemblies using dnSpy</title>
        <description>&lt;p&gt;Needing to debug into Sitecore assemblies isn’t uncommon, whether it be to track down the cause of a bug or simply understand a pipeline better. Traditionally, however, this process has been somewhat complicated by a number of factors:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The JIT generates optimized code for assemblies without PDBs&lt;/li&gt;
  &lt;li&gt;IIS ‘assembly shadow copy’ behaviour makes disabling JIT optimizations difficult&lt;/li&gt;
  &lt;li&gt;Debugging without commercial tools like dotPeek is limited to either low-level IL or basic stack information&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This guide attempts to alleviate all three of these barriers with free and open source tools.&lt;/p&gt;

&lt;h2 id=&quot;step-1-enable-debugging-for-sitecore-assemblies&quot;&gt;Step 1: Enable debugging for Sitecore assemblies&lt;/h2&gt;

&lt;p&gt;Before we can attach a debugger, we need to solve two problems #1 and #2 above:&lt;/p&gt;

&lt;p&gt;As outlined elsewhere (such as &lt;a href=&quot;http://sitecorevn.blogspot.com/2015/04/debugging-optimized-managed-code-in.html&quot;&gt;Anton’s excellent post&lt;/a&gt;), assembly optimizations can be disabled by adding an &lt;code class=&quot;highlighter-rouge&quot;&gt;.ini&lt;/code&gt; file for each relevant assembly with the following contents:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Assembly shadow copying can feature can be temporarily disabled by setting &lt;code class=&quot;highlighter-rouge&quot;&gt;system.web/hostingEnvironment/@shadowCopyBinAssemblies&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Making the changes above (and reverting them afterwards) is busy work that you’ll unlikely be wanting to do when debugging a problem, so I’ve automated the process into a simple PowerShell module (&lt;a href=&quot;https://gist.github.com/richardszalay/59664cd302e66511618f51eaaa77db26&quot;&gt;gist&lt;/a&gt;)&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;Import-Module .\Downloads\IISAssemblyDebugging.psm1
Enable-IISAssemblyDebugging c:\inetpub\wwwroot\sitecore.local

# Alternatively, you can disable optimisations for a limit subset:
Enable-IISAssemblyDebugging c:\inetpub\wwwroot\sitecore.local -Filter &quot;Sitecore*.dll&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;step-2-debug-using-dnspy&quot;&gt;Step 2: Debug using dnSpy&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/0xd4d/dnSpy&quot;&gt;dnSpy&lt;/a&gt; is an excellent, open source, standalone .NET debugger that uses &lt;a href=&quot;https://github.com/icsharpcode/ILSpy&quot;&gt;ILSpy&lt;/a&gt; to allow debugging into decompiled assemblies.&lt;/p&gt;

&lt;p&gt;To begin, download dnSpy and run dnSpy.exe (&lt;strong&gt;as Administrator&lt;/strong&gt;, since we’ll be attaching to IIS)&lt;/p&gt;

&lt;p&gt;Next, select “Attach to Process” from the debug menu (or press CTRL+ALT+P) and select the appropriate w3wp.exe process (the Application Pool can be found in the “Command Line” column)&lt;/p&gt;

&lt;p&gt;Now, select “Close All” from the File menu to clear the assembly list and then “Open” to select the assemblies you wish to debug.&lt;/p&gt;

&lt;p&gt;Finally, set breakpoints and refresh the page in your browser. All the usual debugging views (Locals, Call Stack, etc) are available in the Debug :: Windows menu.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/dnspy-sitecore.png&quot; style=&quot;max-width: 600px&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-3-disable-debugging-for-sitecore-assemblies&quot;&gt;Step 3: Disable debugging for Sitecore assemblies&lt;/h2&gt;

&lt;p&gt;Once we’re done, revert the .ini and shadow copy configuration changes we made earlier:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;Disable-IISAssemblyDebugging c:\inetpub\wwwroot\sitecore.local
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will also need to recycle your app pool to release the lock on the assemblies. Here’s an example using the IISAdministration module:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;(Get-IISAppPool sitecore).Recycle()
&lt;/code&gt;&lt;/pre&gt;
</description>
        <pubDate>Fri, 14 Jun 2019 07:37:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2019/06/14/debugging-sitecore-assemblies/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2019/06/14/debugging-sitecore-assemblies/</guid>
        
        <category>sitecore</category>
        
        <category>debugging</category>
        
        
      </item>
    
      <item>
        <title>Diagnosing missing package assemblies in Helix solutions</title>
        <description>&lt;p&gt;There’s a issue I’ve seen come up again and again when publishing Helix solutions: the assemblies from a NuGet package (usually Unicorn) referenced by a Feature/Foundation module are not included when the Project is published, causing errors during deployment or at runtime. For example:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;error CS0246: The type or namespace name ‘Unicorn’ could not be found (are you missing a using directive or an assembly reference?)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To understand why this happens, we need to look at how assembly resolution works during the build process. For the project being built, the &lt;code class=&quot;highlighter-rouge&quot;&gt;ResolveAssemblyReferences&lt;/code&gt; target is used to collect all of the assembly, project, and nuget references. So far, so good.&lt;/p&gt;

&lt;p&gt;The issue of missing assemblies comes into play when dealing with &lt;em&gt;downstream&lt;/em&gt; references, like the references of referenced projects. For these, the &lt;em&gt;output assemblies&lt;/em&gt; (not projects) are analysed for their references which are then all gathered together to be included in the building-project’s output.&lt;/p&gt;

&lt;p&gt;This works as expected for libraries like Glass, where the assemblies names and types are used directly by the project’s code and so necessitates an assembly reference in that project’s output. Packages like Unicorn cause a problem because in most scenarios the Unicorn assemblies will be required by config files but not actually referenced in code. This precludes them from being included as assembly references and are therefore ignored when it comes to publishing the upstream project.&lt;/p&gt;

&lt;p&gt;Here’s a crude visualisation of this process:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/referenced-assembly-publishing.png&quot; style=&quot;max-width: 600px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are a number of ways this problem can be addressed:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Reference the package directly from the project that you are publishing&lt;/li&gt;
  &lt;li&gt;Have a project reference types from the package’s assembly (make sure they’re meaningful so they don’t get optimized away)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/richardszalay/helix-publishing-pipeline&quot;&gt;Helix Publishing Pipeline&lt;/a&gt; actually &lt;a href=&quot;https://github.com/richardszalay/helix-publishing-pipeline/blob/master/src/targets/Helix.Publishing.Plugins/CollectReferencesFromHelixModules.targets&quot;&gt;automatically applies&lt;/a&gt; a third technique: extend the build process and invoke &lt;code class=&quot;highlighter-rouge&quot;&gt;ResolveAssemblyReferences&lt;/code&gt; on all referenced projects so that their “soft” references can be included. Below is a more general purpose port of the implementation:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Add this to your pubxml or wpp.targets to have all references from referenced projects --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ResolveAssemblyReferencesDependsOn&amp;gt;&lt;/span&gt;
      $(ResolveAssemblyReferencesDependsOn);
      CollectReferencesFromProjectReferences;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ResolveAssemblyReferencesDependsOn&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;Target&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CollectReferencesFromProjectReferences&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;BuildInParallel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;$(BuildInParallel)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Projects=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(ProjectReference)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;RemoveProperties=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DeployOnBuild;PublishProfile&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Targets=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ResolveAssemblyReferences&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;Output&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ItemName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;_ReferencesFromProjectReferences&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;TaskParameter=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TargetOutputs&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/MSBuild&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;ReferencesFromProjectReferences&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(_ReferencesFromProjectReferences)&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Condition=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;'%(_ReferencesFromProjectReferences.CopyLocal)'=='true'&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;nt&quot;&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%(ReferencesFromProjectReferences.FusionName)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ResolvedFrom&amp;gt;&lt;/span&gt;%(ReferencesFromProjectReferences.ResolvedFrom)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ResolvedFrom&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;HintPath&amp;gt;&lt;/span&gt;%(ReferencesFromProjectReferences.FullPath)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/HintPath&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;Redist&amp;gt;&lt;/span&gt;%(ReferencesFromProjectReferences.Redist)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Redist&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;CopyLocal&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/CopyLocal&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Reference&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sun, 21 Apr 2019 00:00:00 +0000</pubDate>
        <link>https://blog.richardszalay.com/2019/04/21/unpublished-module-assemblies/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2019/04/21/unpublished-module-assemblies/</guid>
        
        
      </item>
    
      <item>
        <title>Reentrancy improvements to Minions in Sitecore Commerce 9.1</title>
        <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; In XC91, you’ll no longer need to worry about how long your Minion takes to run. Just override &lt;code class=&quot;highlighter-rouge&quot;&gt;Minion.Execute&lt;/code&gt; rather than &lt;code class=&quot;highlighter-rouge&quot;&gt;Minion.Run&lt;/code&gt; and you’re good to go.&lt;/p&gt;

&lt;p&gt;Back in March of last year, &lt;a href=&quot;https://sitecore.stackexchange.com/a/11004/1173&quot;&gt;I responded to StackExchange question&lt;/a&gt; on the limitations of Sitecore Commerce’s Minion in terms of reentrancy. Put more plainly, in XC9.0 Minions used a “fire and forget” approach for starting each ‘invocation’ which resulted in Minions running twice in parallel when the previous invocation took longer to execute than the configured &lt;code class=&quot;highlighter-rouge&quot;&gt;WakeupInterval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my answer I described two workarounds to the problem, both having a subtle impact on the behavior of Minions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Detect reentrant calls using &lt;code class=&quot;highlighter-rouge&quot;&gt;Interlocked.CompareExchange&lt;/code&gt; and skip them, which may result in an “invocation” being skipped entirely&lt;/li&gt;
  &lt;li&gt;Delay the WakeupInterval pause until after the invocation is complete, which moves the pause from the “start” of the invocation to the end&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The recently released Commerce 9.1 aims to tackle this issue of reentrancy and the implementation actually takes a little from both of the approaches above, as well as handling some additional scenarios. Let’s start with an overview of how Minions are started in XC9.0:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;StartEnvironmentMinionsBlock
  Minion.Start()
    loop forever
      Minion.Run (fire and forget) &amp;lt;-- RunMinion calls this
      WakeupInterval (blocking)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s a visualisation to illustrate the problem this causes:&lt;/p&gt;

&lt;figure style=&quot;max-width: 600px&quot;&gt;
&lt;img src=&quot;/assets/minions-reentrancy-xc90.png&quot; /&gt;
&lt;figcaption style=&quot;font-style: italic; text-align: center&quot;&gt;Minion execution flow in Sitecore Commerce 9.0&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, XC 9.0 starts it’s WakeupInterval delay from the moment Minion.Run is invoked. This is actually fine… for some configurations. The problem arises when Run takes longer than WakeupInterval, and results in Run being executed twice in parallel. If a Minion was not written with parallel execution in mind, it can cause some serious problems.&lt;/p&gt;

&lt;p&gt;I’m happy to report that XC 9.1 fixes all of the flaws in the original design. Starting with the issue described above, Minions now apply the &lt;code class=&quot;highlighter-rouge&quot;&gt;WakeupInterval&lt;/code&gt; delay &lt;em&gt;after&lt;/em&gt; the main method (now called &lt;code class=&quot;highlighter-rouge&quot;&gt;Execute&lt;/code&gt;, with &lt;code class=&quot;highlighter-rouge&quot;&gt;Run&lt;/code&gt; being marked as “obsolete”) completes asynchronously:&lt;/p&gt;

&lt;figure style=&quot;max-width: 600px&quot;&gt;
&lt;img src=&quot;/assets/minions-reentrancy-xc91.png&quot; /&gt;
&lt;figcaption style=&quot;font-style: italic; text-align: center&quot;&gt;Minion execution flow in Sitecore Commerce 9.1&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, it’s now impossible for &lt;code class=&quot;highlighter-rouge&quot;&gt;Execute&lt;/code&gt; to be invoked twice in parallel.&lt;/p&gt;

&lt;p&gt;The changes don’t stop there, though. In XC90 it will also possible to start a minion via the &lt;code class=&quot;highlighter-rouge&quot;&gt;RunMinion&lt;/code&gt; API while it was already running, resulting in it running twice in parallel. The new model not only prevents the same Minion from running twice, but will also skip an invocation if there is another Minion running that includes any of the values from its &lt;code class=&quot;highlighter-rouge&quot;&gt;MinionPolicy.Entities&lt;/code&gt; array. It also doesn’t matter &lt;em&gt;how&lt;/em&gt; the Minion is started: calling the &lt;code class=&quot;highlighter-rouge&quot;&gt;RunMinion&lt;/code&gt; API will still skip execution if the Minion is currently executing on its schedule.&lt;/p&gt;

&lt;p&gt;Here’s a deeper look into the new flow:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;StartEnvironmentMinionsBlock
  Minion.StartAsync() - called by StartEnvironmentMinionsBlock
    Warn if no WakeupInterval set
    loop forever
      await Minion.Process &amp;lt;-- RunMinion calls this
          Skip if policy is marked as running
          Skip if policy with any intersecting `Entities` value is running
          Mark as running
          await Minion.Execute
          Mark as not running
      await WakeupInterval
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To summarise the behaviour changes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Override Minion.Execute instead of Minion.Start&lt;/li&gt;
  &lt;li&gt;As before, handling exceptions is the responsibility of Minion.Execute&lt;/li&gt;
  &lt;li&gt;WakeupInterval now starts after Execute has completed asynchronously&lt;/li&gt;
  &lt;li&gt;Execute will be skipped if the minion is already running (either on a timer or via API)&lt;/li&gt;
&lt;/ol&gt;

</description>
        <pubDate>Wed, 17 Apr 2019 01:37:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2019/04/17/minion-reentry-improvements-in-sitecore-commerce-9-1/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2019/04/17/minion-reentry-improvements-in-sitecore-commerce-9-1/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Patching Sitecore Commerce Rules</title>
        <description>&lt;p&gt;Recently I noticed that the “Number of orders [compares to] [value]” promotion qualification wasn’t working when comparing to 0, which turned out to be because it &lt;em&gt;always&lt;/em&gt; returns false when the order count is 0. Since this was clearly a bug that would eventually be fixed, I wanted to avoid adding a “new” rule as it would require some manual database patching when the fix did eventually come along.&lt;/p&gt;

&lt;p&gt;After a little exploration, I found that it is indeed possible to replace the implementation of existing qualifications and benefits (conditions and actions), so I thought I’d share what I learned.&lt;/p&gt;

&lt;p&gt;Sitecore Commerce uses &lt;code class=&quot;highlighter-rouge&quot;&gt;Sitecore.Framework.Rules&lt;/code&gt; for conditions and actions, so the key is understanding how rule registration is handled in that library. Rules are configured by adding types and exclusions to the registry (&lt;code class=&quot;highlighter-rouge&quot;&gt;IReflectionDiscoveryConfig&lt;/code&gt;), which is then later used by the &lt;code class=&quot;highlighter-rouge&quot;&gt;IEntityDiscoverer&lt;/code&gt; to find available rules at runtime.&lt;/p&gt;

&lt;p&gt;It turns out there are two implementation details that make it easy to swap out a rule:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Only rules &lt;em&gt;names&lt;/em&gt; (applied via attribute) are stored in the promotion entity, not the full type name.&lt;/li&gt;
  &lt;li&gt;Exclusions to the rule regitry are applied at &lt;em&gt;discovery&lt;/em&gt; rather being a &lt;em&gt;removal&lt;/em&gt; of existing configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these two things in mind, all we need to do is apply the same rule name to our condition via the &lt;code class=&quot;highlighter-rouge&quot;&gt;EntityIdentifier&lt;/code&gt; attribute:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;[EntityIdentifier(&quot;CurrentCustomerOrdersCountCondition&quot;)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ZeroSupportingCurrentCustomerOrdersCountCondition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ICustomerCondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ICondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IMappableRuleEntity&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Implementation&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then exclude the original type during configuration:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServiceCollection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Sitecore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rules&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RegisterAssembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Assembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExcludeType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CurrentCustomerOrdersCountCondition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// We're replacing this rule&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it. Since the display text is looked up using the same &lt;code class=&quot;highlighter-rouge&quot;&gt;EntityIdentifier&lt;/code&gt; attribute, it will even appear correctly in the Commerce Business Tools without any other changes.&lt;/p&gt;
</description>
        <pubDate>Tue, 26 Jun 2018 11:53:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2018/06/26/patching-commerce-rules/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2018/06/26/patching-commerce-rules/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Continuous local deployments for Sitecore Commerce Engine</title>
        <description>&lt;p&gt;Commerce Engine’s default configuration poses some interesting problems for deployment, especially for local development workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It has four roles (Ops, Shop, Authoring, and Minions) that each run as their own site/application. Each has their own “startup” configuration, but otherwise load runtime configuration from the database (put their by “Bootstrapping” the Ops role)&lt;/li&gt;
  &lt;li&gt;ASP.NET Core doesn’t support assmebly shadow copies, so the assembly files are locked while the roles are running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post I’ll run through the tricks I use to simplify local development workflow without causing problems for your downstream CI/CD that can come from a custom “file copy” approach in local development.&lt;/p&gt;

&lt;p&gt;By the end of this post we’ll have continuous local deployment triggered on build without affecting build performance, as well as providing an easy way to debug the minion role.&lt;/p&gt;

&lt;h2 id=&quot;publish-profile&quot;&gt;Publish profile&lt;/h2&gt;

&lt;p&gt;To start off, we’ll create a publish profile using the &lt;code class=&quot;highlighter-rouge&quot;&gt;FileSystem&lt;/code&gt; publish method. The target will be the ops role, for reasons that will be explained in the next section, but is otherwise default configuration:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Local.pubxml --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ToolsVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://schemas.microsoft.com/developer/msbuild/2003&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;WebPublishMethod&amp;gt;&lt;/span&gt;FileSystem&lt;span class=&quot;nt&quot;&gt;&amp;lt;/WebPublishMethod&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;LastUsedBuildConfiguration&amp;gt;&lt;/span&gt;Release&lt;span class=&quot;nt&quot;&gt;&amp;lt;/LastUsedBuildConfiguration&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;LastUsedPlatform&amp;gt;&lt;/span&gt;Any CPU&lt;span class=&quot;nt&quot;&gt;&amp;lt;/LastUsedPlatform&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PublishFramework&amp;gt;&lt;/span&gt;net452&lt;span class=&quot;nt&quot;&gt;&amp;lt;/PublishFramework&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;UsePowerShell&amp;gt;&lt;/span&gt;True&lt;span class=&quot;nt&quot;&gt;&amp;lt;/UsePowerShell&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;publishUrl&amp;gt;&lt;/span&gt;C:\inetpub\wwwroot\CommerceOps_Sc9&lt;span class=&quot;nt&quot;&gt;&amp;lt;/publishUrl&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;DeleteExistingFiles&amp;gt;&lt;/span&gt;False&lt;span class=&quot;nt&quot;&gt;&amp;lt;/DeleteExistingFiles&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;sharing-assemblies&quot;&gt;Sharing assemblies&lt;/h2&gt;

&lt;p&gt;The first challenge is the four role directories. Publishing four times &lt;em&gt;will&lt;/em&gt; increase publishing time in a noticable way, especilly if the aim is to do it on each build.&lt;/p&gt;

&lt;p&gt;While it may be tempting to consolidate everything into a single role for development, this can actually complicate your downstream deployment process because your environment policies won’t correlate. Worse, it means that your minion role can’t be isolated for debugging because it’s part of the authoring role.&lt;/p&gt;

&lt;p&gt;The approach I have taken is to configure the four roles to all use the assemblies from the Ops role’s directory, which is done by changing &lt;code class=&quot;highlighter-rouge&quot;&gt;aspNetCore/@processPath&lt;/code&gt; in Web.config to a relative path:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;handlers&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;add&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;aspNetCore&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;verb=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;modules=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AspNetCoreModule&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;resourceType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Unspecified&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/handlers&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;modules&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;runAllManagedModulesForAllRequests=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;remove&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WebDAVModule&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/modules&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;aspNetCore&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;processPath=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;..\CommerceOps_Sc9\Sitecore.Commerce.Engine.exe&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;arguments=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;forwardWindowsAuthToken=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stdoutLogEnabled=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;requestTimeout=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;00:10:00&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stdoutLogFile=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.\logs\stdout&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This approach has two caveats to be aware of:&lt;/p&gt;

&lt;p&gt;Each of the role directories still need a &lt;code class=&quot;highlighter-rouge&quot;&gt;bootstrap\Global.json&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;config.json&lt;/code&gt;. I currently consider these an “initial setup task”, but I may eventually try to deploy/configure them using custom targets in the publish profile.&lt;/p&gt;

&lt;p&gt;Correlating &lt;code class=&quot;highlighter-rouge&quot;&gt;Sitecore.Commerce.Engine.exe&lt;/code&gt; processes to their role becomes more difficult. In reality, though, it will usually only be the authoring role that requires being attached to so there will only be one process to choose from.&lt;/p&gt;

&lt;h2 id=&quot;unlocking-assemblies&quot;&gt;Unlocking Assemblies&lt;/h2&gt;

&lt;p&gt;Continuous deployment is pointless if it can’t overwrite any files, and ASP.NET Core doesn’t support shadow copies. Luckily, IIS has a trick up it’s sleeve: the &lt;code class=&quot;highlighter-rouge&quot;&gt;app_offline.html&lt;/code&gt; file. It’s existence shuts down the application pool and rejects all requests (with a 503 status) until the file is deleted.&lt;/p&gt;

&lt;p&gt;In our scenario, we’ll need four separate app_offline files (one for each site), but that’s easily achievable by hooking into the “Before” and “After” events of the publish. The code below should be placed in &lt;code class=&quot;highlighter-rouge&quot;&gt;Properties\PublishProfiles\Local.wpp.targets&lt;/code&gt; (or directly in &lt;code class=&quot;highlighter-rouge&quot;&gt;Local.pubxml&lt;/code&gt; if you prefer):&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ToolsVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://schemas.microsoft.com/developer/msbuild/2003&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Takes the engine offline so we can override it's assemblies --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;CommerceEngineNodes&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;C:\inetpub\wwwroot\CommerceOps_Sc9&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;CommerceEngineNodes&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;C:\inetpub\wwwroot\CommerceAuthoring_Sc9&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;CommerceEngineNodes&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;C:\inetpub\wwwroot\CommerceMinions_Sc9&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;CommerceEngineNodes&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;C:\inetpub\wwwroot\CommerceShops_Sc9&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;Target&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;BeforePublish&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;WriteLinesToFile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Lines=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;File=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%(CommerceEngineNodes.FullPath)\app_offline.htm&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;Target&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AfterPublish&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Files=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(CommerceEngineNodes -&amp;gt; '%(FullPath)\app_offline.htm')&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;configure-continuous-deployment&quot;&gt;Configure continuous deployment&lt;/h2&gt;

&lt;p&gt;Now that we have the means to easily update our four applications, the next logical step is to automatically deploy the files whenever we build.&lt;/p&gt;

&lt;p&gt;By placing the following in your engine csproj (or &lt;code class=&quot;highlighter-rouge&quot;&gt;Directory.Build.props&lt;/code&gt; if using VS2017), the project will be published to your local instances whenever you build in “Debug” from within Visual Studio. The impact on build time is negligable because unlike right click :: Publish it doesn’t cause a rebuild, so it’s win-win!&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Automatically publish to Properties/PublishProfiles/Local.pubxml on build --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;PropertyGroup&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Condition=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;'$(Configuration)' == 'Debug' and '$(BuildingInsideVisualStudio)' == 'true'&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;DeployOnBuild&amp;gt;&lt;/span&gt;True&lt;span class=&quot;nt&quot;&gt;&amp;lt;/DeployOnBuild&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PublishProfile&amp;gt;&lt;/span&gt;Local&lt;span class=&quot;nt&quot;&gt;&amp;lt;/PublishProfile&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This trick only works for “sdk-style” projects, like those used ASP.NET Core, but there’s a similar approach for legacy web projects is outlined in &lt;a href=&quot;https://github.com/richardszalay/helix-publishing-pipeline&quot;&gt;helix-publishing-pipeline&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;debugging&quot;&gt;Debugging&lt;/h2&gt;

&lt;p&gt;Commerce Engine’s architecture lends itself to test-driven development, and I highly recommend doing it if you’re not already. Having said that, there’s always going to be the need for interactive debugging to confirm any assumptions made about what data will be available at any given time.&lt;/p&gt;

&lt;p&gt;For the version of config.json and Global.json that you commit into source control (ie. part of your Engine project source), I recommend setting them up to run as your Minion environment. I find myself needing to “F5” into the Minion role far more often than any other role. In fact, I tend to disable the Minion app pool in development unless there’s a reason for it to run in the background.&lt;/p&gt;

&lt;p&gt;For authoring, I currenly resort to checking the Application event log for the entry that correlates the IIS AppPool with the process id. Awkward, but it works.&lt;/p&gt;

&lt;h2 id=&quot;bonus-bootstrapping&quot;&gt;Bonus: Bootstrapping&lt;/h2&gt;

&lt;p&gt;Bootstrapping as part of continuous &lt;em&gt;local&lt;/em&gt; deployments is unlikely to be worth the time it takes to fire up the app pool, but if you’re looking to easily bootstrap without using going through Postman I’ve created a PowerShell script that defaults all it’s parameters to local development defaults. Check it out here: &lt;a href=&quot;https://gist.github.com/richardszalay/6f03d8898bf524d4ea26ba99333e80e6&quot;&gt;https://gist.github.com/richardszalay/6f03d8898bf524d4ea26ba99333e80e6&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 06 Jun 2018 11:20:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2018/06/06/commerce-engine-local-deployment/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2018/06/06/commerce-engine-local-deployment/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Adding custom facetable attributes to products in Sitecore Commerce 9</title>
        <description>&lt;p&gt;In this post, I’m going to walk through the steps involved in exposing custom data on a SellableItem as a facet (as displayed on the Storefront’s category page).&lt;/p&gt;

&lt;p&gt;Let’s start with a quick summary of how all these parts are connected, since this alone would have helped immensely when I started looking into making this customisation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Data is added to Sellable Items via Components&lt;/li&gt;
  &lt;li&gt;Fields are exposed to Sitecore via the ConnectSellableItem view&lt;/li&gt;
  &lt;li&gt;Templates are updated to include the custom field&lt;/li&gt;
  &lt;li&gt;The custom field is added to the index&lt;/li&gt;
  &lt;li&gt;A facet item is defined on the indexed field&lt;/li&gt;
  &lt;li&gt;The facet is associated with a Commerce category&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;add-component-to-sellableitem&quot;&gt;Add Component to SellableItem&lt;/h2&gt;

&lt;p&gt;This is the only step that I’m going to omit the details for, both because it involves a fair amount of boilerplate code and because there’s a fairly exhaustive “How to” available from Sitecore: &lt;a href=&quot;https://kb.sitecore.net/articles/083614&quot;&gt;How to extend Catalog system entities schema in Sitecore Experience Commerce 9.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In case that link becomes unavailable, it involves:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Adding a block to &lt;code class=&quot;highlighter-rouge&quot;&gt;IGetEntityViewPipeline&lt;/code&gt; that exposes data as “Properties” to the view and also defines “Actions” that will be handled by another pipeline when triggered&lt;/li&gt;
  &lt;li&gt;Adding a block to &lt;code class=&quot;highlighter-rouge&quot;&gt;IDoActionPipeline&lt;/code&gt; that updates the component based on the action and data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are plenty of examples of these blocks in the stock plugins, so with a little help from ILSpy (or similar) it will all become clear.&lt;/p&gt;

&lt;h2 id=&quot;include-fields-in-commerce-templates&quot;&gt;Include fields in commerce templates&lt;/h2&gt;

&lt;p&gt;The “Update Commerce Templates” button in the ribbon updates the Catalog, Category, and SellableItem templates by querying a specific view for the first available entity and then populates each item by querying the same view again for each entity. This also makes use of the &lt;code class=&quot;highlighter-rouge&quot;&gt;IGetEntityViewPipeline&lt;/code&gt;, with the only real important differences being:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Child views are required (as with Master views) since they map to field sections in Sitecore&lt;/li&gt;
  &lt;li&gt;Properties should be added regardless of whether the data is available, as “Update Commerce Templates” generates the template fields from the first available SellableItem and that might not be relevant to your custom data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For SellableItem, the view is &lt;code class=&quot;highlighter-rouge&quot;&gt;KnownCatalogViewsPolicy.ConnectSellableItem&lt;/code&gt;, so we’ll just include a basic block here:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GetCustomDataBlock&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;PipelineBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntityView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EntityView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntityView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntityView&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommerceContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntityViewArgument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewsPolicy&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KnownCatalogViewsPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entity&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SellableItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isCommerceConnectTemplateUpdate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; 
            &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ViewName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewsPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConnectSellableItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isCommerceConnectTemplateUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fieldSectionView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EntityView&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Fabrikam&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;DisplayName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Fabrikam&quot;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;entityView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ChildViews&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fieldSectionView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CustomComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CustomComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;fieldSectionView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewProperty&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;CustomData&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;DisplayName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Custom Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;RawValue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CustomData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;IsReadOnly&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;IsRequired&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;IsHidden&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In reality it’s likely that you’ll want to combine this block with whatever exposes these properties for the “Details” view since in many use cases the output will be the same.&lt;/p&gt;

&lt;p&gt;Once that is deployed, simply click the “Update Commerce Templates” button and the data will be populated in the catalog items.&lt;/p&gt;

&lt;h2 id=&quot;include-the-data-in-the-index&quot;&gt;Include the data in the index&lt;/h2&gt;

&lt;p&gt;The faceting queries use &lt;code class=&quot;highlighter-rouge&quot;&gt;sitecore_(db)_index&lt;/code&gt;, so we just need to make sure the custom field is included in those. 
All of the base factes are simply defined on   &lt;code class=&quot;highlighter-rouge&quot;&gt;defaultSolrIndexConfiguration&lt;/code&gt;, but free to be more specific if your indexing setup is more complex.&lt;/p&gt;

&lt;p&gt;Here’s an example cloned from the built in ‘brand’ field:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;field&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;fieldName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customdata&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;storageType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;YES&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;indexType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UN_TOKENIZED&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;vectorType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;NO&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;boost=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1f&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;returnType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;settingType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Be aware that using text/UN_TOKENIZED will split multi-word values into multiple facet values and display them in lowercase, so you may want to customise the field configuration if that doesn’t work for you. A &lt;code class=&quot;highlighter-rouge&quot;&gt;returnType&lt;/code&gt; of &lt;code class=&quot;highlighter-rouge&quot;&gt;string&lt;/code&gt;, for example, will prevent any processing occuring.&lt;/p&gt;

&lt;p&gt;You may need to rebuild your index(es), at least for the Commerce tree.&lt;/p&gt;

&lt;h2 id=&quot;define-a-custom-facet&quot;&gt;Define a custom facet&lt;/h2&gt;

&lt;p&gt;To define a new facet, simply create an item in  &lt;code class=&quot;highlighter-rouge&quot;&gt;/sitecore/system/Settings/Buckets/Facets&lt;/code&gt; using the &lt;code class=&quot;highlighter-rouge&quot;&gt;Facet&lt;/code&gt; template, and set the &lt;code class=&quot;highlighter-rouge&quot;&gt;Field Name&lt;/code&gt; property to match your indexed &lt;code class=&quot;highlighter-rouge&quot;&gt;fieldName&lt;/code&gt; (“customdata” in our example above).&lt;/p&gt;

&lt;p&gt;The name of the facet item isn’t too important, but I tend to prefix it with the same “namespace” that I use else where. For example “Fabrikam Custom Data”&lt;/p&gt;

&lt;h2 id=&quot;apply-the-custom-facet-to-the-category&quot;&gt;Apply the custom facet to the category&lt;/h2&gt;

&lt;p&gt;Finally, to display the facet on a category page edit the Category item in &lt;code class=&quot;highlighter-rouge&quot;&gt;/Commerce/Catalogs&lt;/code&gt; and add the facet to the &lt;code class=&quot;highlighter-rouge&quot;&gt;Runtime Search Facets&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;There is unfortunately no installation specific “default” value for this field, so if the facet applies globally you either need to manually change each category or update the standard values for &lt;code class=&quot;highlighter-rouge&quot;&gt;/sitecore/templates/Commerce/Catalog/CommerceSearchSettings&lt;/code&gt; and remember to change it again when you perform a future Commerce upgrade.&lt;/p&gt;
</description>
        <pubDate>Mon, 23 Apr 2018 00:00:00 +0000</pubDate>
        <link>https://blog.richardszalay.com/2018/04/23/commerce-sellableitem-facet/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2018/04/23/commerce-sellableitem-facet/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Improving Sitecore Commerce exception logging with Demystifier</title>
        <description>&lt;p&gt;Sitecore Experience Commerce’s “Engine” microservice makes heavy use of asynchronous programming in everything from API controllers and commands to pipelines and data access. While this makes the most out of your server resources by minimising thread contention, “with great power comes terrible stack traces”.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;System.Exception: Error processing block: Core.block.FindEntityInMemoryCache ---&amp;gt; System.NullReferenceException: Object reference not set to an instance of an object.
   at Sitecore.Commerce.Core.Caching.EntityMemoryCachingPolicy.GetCachePolicy(CommerceContext commerceContext, Type arg)
   at Sitecore.Commerce.Core.FindEntityInMemoryCacheBlock.&amp;lt;Run&amp;gt;d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Framework.Pipelines.ReflectionPipelineBlockRunner.&amp;lt;InvokeBlock&amp;gt;d__2.MoveNext()
   --- End of inner exception stack trace ---
   at Sitecore.Framework.Pipelines.ReflectionPipelineBlockRunner.&amp;lt;InvokeBlock&amp;gt;d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Framework.Pipelines.BasePipelineBlockRunner.&amp;lt;Run&amp;gt;d__3`1.MoveNext()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The essence of the exception is shrouded by implementation details of the async/await process, making it very difficult to undersatnd.&lt;/p&gt;

&lt;p&gt;Ben Adams decided to tackle this problem head on a few months back and created &lt;a href=&quot;https://github.com/benaadams/Ben.Demystifier&quot;&gt;Demystifier&lt;/a&gt;, which transforms known stack frame patterns into something that resembles the original code. The concept has been so popular that it has been &lt;a href=&quot;https://www.ageofascent.com/2018/01/26/stack-trace-for-exceptions-in-dotnet-core-2.1/&quot;&gt;merged into .NET Core 2.1&lt;/a&gt; and will likely make it into a future release of the .NET Framework.&lt;/p&gt;

&lt;p&gt;For the time being, though, beautiful stack trackes require a little work. Thankfully, Nicholas Blumardt has developed &lt;a href=&quot;https://github.com/nblumhardt/serilog-enrichers-demystify&quot;&gt;Serilog integration for Demystifier&lt;/a&gt; that reduces the integration process to a few steps.&lt;/p&gt;

&lt;p&gt;First, we need to add the &lt;code class=&quot;highlighter-rouge&quot;&gt;Serilog.Enrichers.Demystify&lt;/code&gt; package to our &lt;code class=&quot;highlighter-rouge&quot;&gt;Sitecore.Commerce.Engine&lt;/code&gt; project:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;Install-Package Serilog.Enrichers.Demystify -IncludePrerelease&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next we need to update the &lt;code class=&quot;highlighter-rouge&quot;&gt;LoggerConfiguration&lt;/code&gt; (included in the Startup constructor in the default Commerce SDK) to use the enricher:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromLogContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithDemystifiedStackTraces&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Add this line&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;With&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ScLogEnricher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s it! The next time you see an exception in your log, it will look something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;System.Exception: Error processing block: Core.block.FindEntityInMemoryCache ---&amp;gt; System.NullReferenceException: Object reference not set to an instance of an object.
   at EntityMemoryCachingPolicy Sitecore.Commerce.Core.Caching.EntityMemoryCachingPolicy.GetCachePolicy(CommerceContext commerceContext, Type arg)
   at async Task&amp;lt;FindEntityArgument&amp;gt; Sitecore.Commerce.Core.FindEntityInMemoryCacheBlock.Run(FindEntityArgument arg, CommercePipelineExecutionContext context)
   at async Task&amp;lt;object&amp;gt; Sitecore.Framework.Pipelines.ReflectionPipelineBlockRunner.InvokeBlock(IPipelineBlock block, IPipelineExecutionContext context, object current)
   --- End of inner exception stack trace ---
   at async Task&amp;lt;object&amp;gt; Sitecore.Framework.Pipelines.ReflectionPipelineBlockRunner.InvokeBlock(IPipelineBlock block, IPipelineExecutionContext context, object current)
   at async Task&amp;lt;TOutput&amp;gt; Sitecore.Framework.Pipelines.BasePipelineBlockRunner.Run&amp;lt;TOutput&amp;gt;(string name, object input, IPipelineExecutionContext context)&lt;/code&gt;&lt;/pre&gt;

</description>
        <pubDate>Thu, 15 Mar 2018 00:00:00 +0000</pubDate>
        <link>https://blog.richardszalay.com/2018/03/15/sitecore-commerce-demystifier/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2018/03/15/sitecore-commerce-demystifier/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Decomposing Monolothic ERP Exports using Sitecore Commerce 9 Pipelines</title>
        <description>&lt;p&gt;Exporting orders is a critical part of integrating an ecommerce website with an ERP system, and the integration often takes the form of an XML file or some other large data structure.&lt;/p&gt;

&lt;p&gt;Sitecore’s Experience Commerce 9 finally brings the entire model into Sitecore Engine with it’s testable architecture, but generating a complete XML structure from the available data involves numerous dependencies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Billing information is obtained via FederatedPaymentComponent on the Order&lt;/li&gt;
  &lt;li&gt;Shipping information is obtained via PhysicalFulfillmentComponent on the Order, but this can change for more complex shipping scenarios&lt;/li&gt;
  &lt;li&gt;Customer contact address information is obtained via a AddressComponent on the Customer, which is in turn dereferenced with GetCustomerCommand using the customer id extracted from the ContactComponent on the Order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attempting to test a monolothic implementation of such a mapping would involve significant setup of both data and mocks, resulting in tests that pass but that are difficult to understand and maintain. Refactoring the mapping process into a series of services that make use of Dependency Injection would help, but isn’t particularly in line with Commerce Engine’s architecture.&lt;/p&gt;

&lt;p&gt;This article will explore making use of Sitecore Commerce Pipelines to decompose the mapping process. Pipelines are not only composable but also individually support dependency injection, and using pipeline blocks to mutate the pipeline arguments has significant precence within the core plugins.&lt;/p&gt;

&lt;p&gt;Let’s start with a monolothic implementation. It’s still testable due to the use of interfaces and the separation of the mapping into its own public method, but there’s a lot happening which means a lot to setup in our tests.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyErpOrderExport&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PipelineBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GetCustomerCommand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getCustomerCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ISubmitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyErpOrderExport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetCustomerCommand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getCustomerCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;ISubmitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getCustomerCommand&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getCustomerCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateOrderDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileSubmissionPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateOrderElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;order&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateCustomerElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateBillingElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateShippingElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateLineItemsElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateCustomerElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contactComponent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContactComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contactComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CustomerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getCustomerCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommerceContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customerId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddressComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;party&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Party&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;party&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateBillingElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateShippingElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateLineItemsElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, you could argue that a few more of these methods could be made public to increase testability, and you’d be right, but it wouldn’t diminish the fact that this ends up being a big class does a lot, increasing the cognitive load required to reason about it.&lt;/p&gt;

&lt;p&gt;Let’s look at what it might look like if we built up the XML in pipeline blocks instead:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyErpOrderExport&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PipelineBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IBuildErpOrderDocumentPipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildErpOrderDocumentPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ISubmitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyErpOrderExport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IBuildErpOrderDocumentPipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildErpOrderDocumentPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ISubmitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buildErpOrderDocumentPipeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildErpOrderDocumentPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateOrderDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submitMyErpFilePipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateOrderElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocumentArgument&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;order&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildErpOrderDocumentPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Our top level orchestrator now simply moves data between two pipelines interfaces, making it very testable itself, but the real value is how much easier it is to manage each aspect of the document being built. Here’s &lt;code class=&quot;highlighter-rouge&quot;&gt;AddBillingElementBlock&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddBillingElementBlock&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PipelineBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocumentArgument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocumentArgument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocumentArgument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocumentArgument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommercePipelineExecutionContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Ain't no party like a...&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;billingParty&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FederatedPaymentComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BillingParty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;billingInformation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;XElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;address1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;billingParty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Address1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Each block in the pipeline can now add its own elements to the document, and can be tested in isolation.&lt;/p&gt;

&lt;p&gt;And finally, we compose all the blocks together in &lt;code class=&quot;highlighter-rouge&quot;&gt;ConfigureSitecore&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Sitecore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Pipelines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddPipeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IBuildMyErpOrderDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BuildMyErpOrderDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddCustomerElementBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddBillingElementBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddShippingElementBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
</description>
        <pubDate>Tue, 20 Feb 2018 20:00:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2018/02/20/sitecore-commerce-erp-decomposition/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2018/02/20/sitecore-commerce-erp-decomposition/</guid>
        
        <category>sitecore</category>
        
        <category>experience-commerce-9</category>
        
        
      </item>
    
      <item>
        <title>Upgrading Buggy Bits to .NET Core - Part 2: Windows Tooling</title>
        <description>&lt;p&gt;This series attempts to update Tess Ferrandez’ Debugging Labs to be applicable to .NET Core so that they can continue to benefit developers everywhere as .NET Core becomes the norm.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;/2017/09/05/buggy-bits-core-part-1/&quot;&gt;Part 1&lt;/a&gt;, we upgraded the codebase to ASP.NET Core. In this part, we’ll take a look at the different diagnostic tools and approaches used by the labs and how they’ve been changed in Windows 10.&lt;/p&gt;

&lt;h2 id=&quot;load-testing&quot;&gt;Load Testing&lt;/h2&gt;

&lt;p&gt;tinyget is still available via the &lt;a href=&quot;https://support.microsoft.com/en-us/help/840671/the-iis-6-0-resource-kit-tools&quot;&gt;IIS 6.0 (!) Resource Kit Tools&lt;/a&gt;, but if you’re looking for a more modern Windows CLI for HTTP load testing you might be out of luck.&lt;/p&gt;

&lt;p&gt;There are a number of popular cross platform HTTP loading tools available like &lt;a href=&quot;http://jmeter.apache.org/&quot;&gt;JMeter&lt;/a&gt;, &lt;a href=&quot;https://github.com/rakyll/boom&quot;&gt;boom&lt;/a&gt;, and &lt;a href=&quot;https://github.com/tsenart/vegeta&quot;&gt;vegeta&lt;/a&gt;, but these require additional runtimes like Java and Go to be installed.&lt;/p&gt;

&lt;p&gt;Luckily, the Windows Subsystem for Linux means we are no longer limited by tooling availability for Windows. If you have Bash for Windows installed, you can installing a tool like &lt;a href=&quot;https://github.com/wg/wrk&quot;&gt;wrk&lt;/a&gt; using &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo apt install wrk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;wrk in particular has a similar syntax to tinyget, so aligns well with the original labs:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wrk --threads 30 --duration 10 http://localhost:12345
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;monitoring-memory&quot;&gt;Monitoring Memory&lt;/h2&gt;

&lt;p&gt;Monitoring process memory hasn’t changed terribly, but in addition to using Performance Monitor you can also view memory (private working set is your best choice) in Task Manager by right clicking on the headers of the Details tab and selcting “Select columns”.&lt;/p&gt;

&lt;p&gt;For detailed information on the GC, the situation is slightly tricker. .NET Core &lt;a href=&quot;https://github.com/dotnet/corefx/issues/3906&quot;&gt;does not currently support performance counters&lt;/a&gt;, so there’s no way to &lt;em&gt;passively&lt;/em&gt; read GC generation sizes from outside the process. The only choice you have is to attach a debugger (see below) and use SOS’s &lt;code class=&quot;highlighter-rouge&quot;&gt;!HeapStat&lt;/code&gt; command.&lt;/p&gt;

&lt;h2 id=&quot;user-mode-memory-dumps&quot;&gt;User-mode Memory Dumps&lt;/h2&gt;

&lt;p&gt;The various approaches to creating memory dumps now have &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/choosing-the-best-tool&quot;&gt;official documentation&lt;/a&gt;, though &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/adplus&quot;&gt;ADPlus&lt;/a&gt; is still recommended for the majority of usecases. ADPlus is largely unchanged in terms of CLI options, despite now being an executable rather than a vbs script, and still ships with the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/index&quot;&gt;Debugging Tools for Windows&lt;/a&gt;. Just keep in mind that the &lt;code class=&quot;highlighter-rouge&quot;&gt;-iis&lt;/code&gt; argument should be replaced with &lt;code class=&quot;highlighter-rouge&quot;&gt;-pn dotnet.exe&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;-p &amp;lt;PID&amp;gt;&lt;/code&gt; since .NET Core runs its own web server.&lt;/p&gt;

&lt;p&gt;For crash dumps, there is now also the option of having Windows Error Reporting &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181(v=vs.85).aspx&quot;&gt;generate them automatically&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Task Manager now has support for generating user dumps, which can be useful for hang scenarios. Simply right click any process in the “Processes” or “Details” tab and select “Create dump file”&lt;/p&gt;

&lt;h2 id=&quot;debugger&quot;&gt;Debugger&lt;/h2&gt;

&lt;p&gt;WinDBG is still the built-for-purpose application for this type of debugging, and it still ships with the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/index&quot;&gt;Debugging Tools for Windows&lt;/a&gt;. However, there’s also a &lt;a href=&quot;https://blogs.msdn.microsoft.com/windbg/2017/08/28/new-windbg-available-in-preview/&quot;&gt;complete refresh of WinDBG&lt;/a&gt; in development that you can &lt;a href=&quot;https://www.microsoft.com/en-us/store/p/windbg/9pgjgd53tn86&quot;&gt;currently preview via the Windows Store&lt;/a&gt;. It contains a tonne of UI improvements, including a Local/Watch window and quick access to things like threads and the callstack.&lt;/p&gt;

&lt;p&gt;Visual Studio .NET also has reasonable native debugging support and can still load SOS, so you might want to try that if you’d prefer to stay in your warm IDE blanky.&lt;/p&gt;

&lt;p&gt;Regardless of your choice of debugging interface, you’ll still need to load the SOS module that ships with .NET Core. Once you’ve opened a user dump, you can load SOS by typing:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.loadby sos coreclr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Meaning “load the ‘sos’ module from the same directory as the ‘coreclr’ module”)&lt;/p&gt;

&lt;p&gt;Fun fact: SOS stands for Son of Strike and is named after Strike, the original debugging tools used internally by Microsoft while developing .NET which at that stage was still codenamed “Lightning”.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll see how the tools and techniques translate to debugging .NET Core on Linux.&lt;/p&gt;
</description>
        <pubDate>Fri, 08 Sep 2017 09:20:16 +0000</pubDate>
        <link>https://blog.richardszalay.com/2017/09/08/upgrading-buggy-bits-to-net-core-part-2-windows-tooling/</link>
        <guid isPermaLink="true">https://blog.richardszalay.com/2017/09/08/upgrading-buggy-bits-to-net-core-part-2-windows-tooling/</guid>
        
        <category>sitecore</category>
        
        
      </item>
    
  </channel>
</rss>
