tag:blogger.com,1999:blog-48225764084094284172024-03-29T14:29:39.271+11:00Sitecore and MoreSitecore, .NET, and Headless development blogJason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.comBlogger60125tag:blogger.com,1999:blog-4822576408409428417.post-39744456032779157732024-03-22T21:19:00.003+11:002024-03-22T21:20:47.864+11:00Sitecore GraphQL query - children<p>I thought I'd put up a friendly reminder for those who know, or some useful information for those who don't: using <code>children</code> in your GraphQL will (by default) only return 10 items, however the <code>children</code> keyword in Sitecore GraphQL is in fact a function! With this information you can find that you can filter and paginate your list of child items.</p>
<p>While the vast majority of examples you find are something like this:</p>
<script src="https://gist.github.com/moo2u2/3f16a32ec0e60588e996433ba89a582f.js"></script>
<p>You can actually write it as:</p>
<script src="https://gist.github.com/moo2u2/f30a97fa281c0f07efe40f2294f8eff0.js"></script>
<p>There really don't seem to be that many examples around which use these features of <code>children</code> (though there are many showing similar parameters for <code>search</code>), so hope this helps someone!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-85610733604739472112024-03-07T17:33:00.009+11:002024-03-14T11:43:02.455+11:00OrderCloud Variants Deep-Dive<p>In this article I'd like to dive a bit deeper into the concept of Variants (and by assocation "Variant" Specs and Spec Options) in OrderCloud. While there is a bit of information around on the web (natually <a href="https://ordercloud.io/knowledge-base/working-with-specs" rel="nofollow" target="_blank">on the OrderCloud website iself</a>) it's all fairly high level and there are a few things I discovered on an implementation recently that I thought were worth sharing.</p><p>Let's start with a quick recap of what (Variant) Specs, Spec Options, and Variants are:</p><p></p><ol style="text-align: left;"><li>Product: Sellable/purchase-able item<br />example: shirt, pants</li><li>(Variant) Spec: A (re-usable across Products) way of splitting a Product (or multiple products) into different options<br />example: Size, Colour</li><li>Spec Option: Options a user can select to configure their Product<br />example: S / M / L; red / green / blue</li><li>Variant: Each variation of a "configurable" Product, generally with its own SKU<br />examples: Small green shirt, medium blue shirt; medium green pants, large blue pants, etc.</li></ol><p>So as you can probably tell from the examples above, Specs will generally be options which are presented to the user when viewing a specific product, and generally presented in the form of a radio list or dropdown(s); once these have been selected by the user, a specific Variant will be shown/ordered. </p><p>As mentioned in the <a href="https://ordercloud.io/knowledge-base/working-with-specs#variant-spec" rel="nofollow" target="_blank">OrderCloud documentation on Variant Specs</a>, it is worth noting:</p><blockquote>Variant specs must be marked as <code class="chakra-code css-eap42n">Required</code>
as the product object by itself no longer represents a unique product,
but as housing for related variant products and cannot be added to an
order without specifying a specific variant product.</blockquote><h3 style="text-align: left;">Selecting Your Specs (and therefore Variants)</h3><p>You might think, given the example above (size + colour), that it's going to be quite obvious how you will go about splitting your Product into different Variants. Depending on how fleshed-out and/or specific your business requirements are this indeed may be the case, however it is not a guarantee. An important consideration will be: <b><i>how many of the available combinations will actually be purchaseable</i></b>? Will Products actually be available in small, medium, and large for all those colour combinations? When generating Variants using multiple Specs, on your frontend you will need to handle the dynamic showing and hiding of the different variant dropdown options. There is also no point in generating a whole bunch of Variants, many of which you will then need to disable. On that note - per the OrderCloud documentation <a href="https://ordercloud.io/knowledge-base/working-with-specs#generate-variants" rel="nofollow" target="_blank">Generate Variants</a> - this is how you would need to handle any "invalid" Variants (Spec Option combinations):</p><blockquote>In some instances, one or more of the generated variants may not represent a valid product. To manage this, any unwanted variants can be patched to have their <code class="chakra-code css-eap42n">Active</code> property set to <code class="chakra-code css-eap42n">false</code>.</blockquote><p>Let me provide a different example than the standard above: what if, instead, your Product is tickets to a show (imagine a touring comedy show) and you need to be able to allow users to select their location and date. Let's say that there will be multiple locations and each location will have multiple dates available (eg. Sydney on 24th and 25th March 2024, Melbourne 15th and 18th April, etc.). Does it make sense to have Specs/Variants for both Location <b><i>and</i></b> date, or does it make more sense to have a combination (location + date, call it "session") and have Sydney 24th, Sydney 25th, Melbourne 15th, etc.? In this case I would argue that it makes no sense to separate the specs into Location and Date, but to group them into a single Variant Spec and a single dropdown on the frontend. This may, however, depend on into which other systems you are integrating.</p><h3 style="text-align: left;">Product vs Variant Info</h3><p>There is an important decision that needs to be made, or at least an important requirement which should be documented before selecting your Specs or generating your Variants: what data needs to live at the Product level vs what data needs to live at the Variant level. Both <a href="https://ordercloud.io/api-reference/product-catalogs/products/create" rel="nofollow" target="_blank">Product</a> and <a href="https://ordercloud.io/api-reference/product-catalogs/products/save-variant" rel="nofollow" target="_blank">Variant</a> (like most OC entities) contain <span style="font-family: courier;">Name</span>, <span style="font-family: courier;">Description</span>, and <span style="font-family: courier;">xp</span> properties allowing you to store information in either entity; in addition to these properties is <a href="https://ordercloud.io/api-reference/product-catalogs/price-schedules" rel="nofollow" target="_blank">Price</a>, which is a separate entity. An important thing to note (more on this below) is that there are occasions in which you will need to re-populate a Variant's data. Since everything in OC is done via API this is not necessarily painful, just something to be aware of.</p><p>Also as a part of this exercise it is important to decide / document whether <i>inventory </i>should be tracked at Product or Variant level. If you wish to <a href="https://ordercloud.io/knowledge-base/inventory-management#scenario-4-track-inventory-on-all-product-variations" rel="nofollow" target="_blank">track inventory at the Variant level</a>, you will simply have to set:</p><p><code>Product.Inventory.VariantLevelTracking = true</code></p>
<p>As mentioned above, part of the requirements gathering should include documenting which information is common between variantions of a Product, and which data is specific to the Variant.</p><p></p><ul style="text-align: left;"><li>Shared data - which should be assigned to the <b>Product</b> - could include such things as: as a general description of the product, a generic image, weight of an item (if it's the same for all variations), or presenter or duration of a show. </li><li>Variable data - which should be assigned to the <b>Variant </b>- could include such things as: a more specific image, measurements of an item of clothing (if Spec is size), venue information for a show (if Spec is location)</li></ul><p></p><p><b>It is important to keep in mind</b>: it may seem obvious, but for <a href="https://ordercloud.io/knowledge-base/advanced-querying" rel="nofollow" target="_blank">searching or sorting while retrieving a list of Products</a>, you will need to use <i>Product-level data</i>. This may involve <i>duplication of data</i>, which often feels wrong<i>. </i>So, for example, if you want to show a list of Products on a PLP and allow the user to filter the list on a range of locations or dates (which could be Variant data) or sort by price (which would be in the Price Schedule) you will need to add these to the Product <span style="font-family: courier;">xp</span> which can be used in <a href="https://ordercloud.io/knowledge-base/advanced-querying" rel="nofollow" target="_blank">the query</a>.</p><h3 style="text-align: left;">Adding and Removing Variants ad-hoc</h3><p>Continuing with the second Product example above (the touring show with "session" Variants), let's explore how OrderCloud handles the ad-hoc addition and removal of new Spec Options (ie. if shows sell out and so new sessions are added, or old shows are hidden/removed).</p><ol style="text-align: left;"><li>First we <a href="https://ordercloud.io/api-reference/product-catalogs/products/create" rel="nofollow" target="_blank">create our Product</a> (call it "Jason's Australian tour")</li><li>Next we <a href="https://ordercloud.io/api-reference/product-catalogs/specs/create" rel="nofollow" target="_blank">add our (Variant) Spec</a> (called "AU tour sessions")</li><li>We then add <a href="https://ordercloud.io/api-reference/product-catalogs/specs/create-option" rel="nofollow" target="_blank">2x Spec Options</a> to start with:</li><ol><li>Sydney 24th March 2024</li><li>Melbourne 15th April 2024</li></ol><li>We <a href="https://ordercloud.io/api-reference/product-catalogs/specs/save-product-assignment" rel="nofollow" target="_blank">assign our Spec to the Product</a></li><li>Finally, we <a href="https://ordercloud.io/api-reference/product-catalogs/products/generate-variants" rel="nofollow" target="_blank">generate our Variants</a></li></ol><p>The output will be our initial list of 2 sessions which can be purchased. </p><p><code>GET /me/products/:productID/variants</code></p><script src="https://gist.github.com/moo2u2/17c149eac270b5b4c3b872b58e61e529.js"></script>
<p>Because we merged our location and dates together into a single Spec Option, we don't have to remove or disable anything here. We do, however, need to <a href="https://ordercloud.io/api-reference/product-catalogs/products/save-variant" rel="nofollow" target="_blank">update the Variant</a> to include <span style="font-family: courier;">Name</span>, <span style="font-family: courier;">Description</span>, and any <span style="font-family: courier;">xp</span> we wish to use.</p>
<script src="https://gist.github.com/moo2u2/3ac04d69369f0f006f8f46ea0bd92dc1.js"></script>
<p>Next up, let's imagine our tour is doing so well in Sydney that it's sold out and we need more sessions!</p><ol start="6" style="text-align: left;"><li><a href="https://ordercloud.io/api-reference/product-catalogs/specs/create-option" rel="nofollow" target="_blank">Add one more session</a> (Sydney 28th March 2024)</li><li><a href="https://ordercloud.io/api-reference/product-catalogs/products/generate-variants" rel="nofollow" target="_blank">Generate Variants</a> (no "overwrite existing")</li></ol><p>Now we have 3 Variants, with no changes to the existing previous 2.</p><script src="https://gist.github.com/moo2u2/d106d56fcba986e1fd0114a25707c269.js"></script><p>Let's say that it's now after 24th March and we want to stop showing the first option. If you recall above, the <b>correct</b> way to handle this is to set the <code>Active</code> property of the Variant to <code>false</code>. For the purposes of experimentation let's go ahead and totally remove that Spec Option just to see what happens.</p><p></p><ol start="8" style="text-align: left;"><li><a href="https://ordercloud.io/api-reference/product-catalogs/specs/delete-option" rel="nofollow" target="_blank">Remove Spec Option</a> (Sydney 24th March)</li><li><a href="https://ordercloud.io/api-reference/product-catalogs/products/generate-variants" rel="nofollow" target="_blank">Generate Variants</a> (no "overwrite existing")</li></ol>
<script src="https://gist.github.com/moo2u2/70cd44349bb72c995541e3b07ec871f7.js"></script>
<p>Interesting... at this point we still have 3 Variants, but 1 (the removed one) has null data. If you ever find you have a Variant without any Specs in your array, that's probably what's happened (you'll want to regenerate your Variants with "overwrite"). Let's go ahead and try with "overwrite existing".</p><ol start="10" style="text-align: left;"><li><a href="https://ordercloud.io/api-reference/product-catalogs/products/generate-variants" rel="nofollow" target="_blank">Generate Variants</a> (<b><i>with</i></b> "overwrite existing")</li></ol>
<script src="https://gist.github.com/moo2u2/e149453114608c425f177e87809ea111.js"></script>
<p>And we're back to the 2 remaining Variants as expected, however with none of the data (Name, Description, xp) present. This highlights one of the challenges of working with Variants, and why we should use the <code>active</code> property instead of removing Spec Options and regenerating Variants. Also keep in mind that there are other cases where you will need to regenerate Variants, such as changing Spec Option ID, so try to think about your Variants in advance! Rest easy, however, as simply changing some details of a Spec / Spec Option such as Name or Description (or even PriceMarkup) will not mean that you need to regenerate Variants, so no need to stress if you make a typo.</p>
<h3 style="text-align: left;">Pricing</h3><p>Perhaps unfortunately for some (myself included), a Price Schedule can only be assigned at the Product level, and not to a Variant (see my next post on other options which support Price Schedules). There is a workaround, however, in the form of <a href="https://ordercloud.io/knowledge-base/advanced-querying#price-markup" rel="nofollow" target="_blank">price markup</a> (which includes the fields <span style="font-family: courier;">PriceMarkupType</span> and <span style="font-family: courier;">PriceMarkup</span>). Despite the name, it does not need to be an increase since it can be set to a negative value. One major limitation you should be aware of at this point: <b>price markups do not support multi-currency</b>.</p><p>Somewhat unintuitively (at least in my opinion) the price markup can only be set on a Spec Option (which is used to generate the Variant). It can be retrieved from the Variant under the <span style="font-family: courier;">Specs</span> array property. Once you get your head around the fact that pricing is set and retrieved at the Spec Option level, it makes sense that when <a href="https://ordercloud.io/api-reference/orders-and-fulfillment/line-items/create" rel="nofollow" target="_blank">adding a line item to your Order</a>, you must pass the selected Spec Options (and the appropriate Variant is automatically calculated by OrderCloud). </p><p>As the <a href="https://ordercloud.io/knowledge-base/advanced-querying#price-markup" rel="nofollow" target="_blank">price markup documentation</a> includes some good examples of the various price markup options, let's have a look at what happens if we have a Variant generated from multiple Spec Options and add that as a Line Item to our Order:</p><p></p><ol style="text-align: left;"><li>Product - Price $10</li><li>Spec Option Medium - PriceMarkupType <code>AmountTotal</code> - PriceMarkup 2</li><li>Spec Option Black - PriceMarkupType <code>Percentage</code> - PriceMarkup -50</li><li>Final line item price: $7</li></ol><p>Interestingly this seems to indicate a <code>PriceMarkupType</code> of <code>Percentage</code> is executed first.</p><p>Should your requirement to be to handle <i>amounts</i> before <i>percentage</i>, the OrderCloud support team have recommended changing the markup type of Percentage to be an auto-apply line item level promotion. I haven't tried this out, but it seems like a good workaround.</p><p>Should you need to change the PriceMarkup on your Spec Options, these are reflected immediately in the Cart (ie unsubmitted Order) but do not affect submitted Orders, as one would expect.</p><h3 style="text-align: left;">Events</h3><p><b>Product Sync</b> is triggered on (at least):</p><ol style="text-align: left;"><li>Assigning a Spec to Product</li><li>Updating a Spec already assigned to a Product</li><li>Generating Variants</li></ol><p></p><p><i>if the Product data changes</i> - ie it will not be triggered if your 'generate variants' does not actually overwrite/modify anything. The resulting message contains the data of the updated Product.</p>
<p>You can see a sample of a message below:</p>
<script src="https://gist.github.com/moo2u2/10df3e95bd2f440933ce7a9edd3ed5dd.js"></script>
<h3>Conclusion</h3><div style="text-align: left;">(Spec) Variants are a quick and easy way to split your Products - with options that users can choose - into multiple options each with their own SKU. It's a great option for basic usage, but does have its limitations for more complex scenarios; most notably: there are situations where Variants must be re-generated, meaning they lose their data (which needs to be re-added); pricing is not very easy to work with and does not support multi-currency. You should definitely think through your Spec/Option/Variant strategy as you document business requirements and not leave it to the last minute. </div><h3>Alternatives</h3>
<p>Stay tuned to my next article where I will discuss other options and how they compare!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-54894127312014606212023-11-25T20:51:00.010+11:002023-11-30T13:18:57.572+11:00The SUGCON ANZ 2023 Experience<p>That's a wrap for SUGCON ANZ 2023 in sunny Brisbane! I was fortunate enough to be able to make it, and as my first experience of Brisbane it was lovely to have been to spend some time before the event running along the banks of the Brisbane River on the Thursday morning after I landed, as well as experience some of the evening atmosphere on the Thursday evening going out to dinner with some of the crew. What a great city! It was also fantastic to see so many familiar faces, and meet a bunch of new ones as well. Everyone was super friendly and up for a chat - plus it helps that we all have (at least) one thing in common!</p><p>I thought it would be worth sharing a summary of events for those who couldn't make it - hope to meet some more of you next year!</p>
<h3 style="text-align: left;">Day 1 (Thursday)</h3>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3735xlYjSta4jIBda-CDSn9sYkbF6zXUgIqhpD3rtT7RE4X99yKmd1-s4bvCahgN-5ce03nHZOoYttK9ViKSjhRfZnMq1tZHJ9FicO55FtA-n68ClOU8a4MsiADD6EDOiPz9pVxagG4zadcaNszJtrHdLwSAKOfjYOoYLyK6hyzTfP4fnIsCMUJVowBe0/s4080/PXL_20231123_030312471.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3735xlYjSta4jIBda-CDSn9sYkbF6zXUgIqhpD3rtT7RE4X99yKmd1-s4bvCahgN-5ce03nHZOoYttK9ViKSjhRfZnMq1tZHJ9FicO55FtA-n68ClOU8a4MsiADD6EDOiPz9pVxagG4zadcaNszJtrHdLwSAKOfjYOoYLyK6hyzTfP4fnIsCMUJVowBe0/w200-h151/PXL_20231123_030312471.jpg" width="200" /></a></div><p><b><a href="https://www.linkedin.com/in/richardhauer/" rel="nofollow" target="_blank">Richard Hauer</a></b> - as one of the organisers - kicked things off with a brief welcome, and was quickly followed by <b><a href="https://www.linkedin.com/in/gusq/" rel="nofollow" target="_blank">Gus Quiroga</a></b> (Sitecore AVP) who invited us all to be "open to inspiration", providing a few examples of a few historical figures who proved that we “don’t have to re-invent, only to re-imagine”.</p>
<p style="clear: both;"><b><a href="https://www.linkedin.com/in/vigneshvishwanath/" rel="nofollow" target="_blank"></a></b></p><div class="separator" style="clear: both; text-align: center;"><b><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqb8QDcxta65eajG7TrOpCnoiDmV3tl2QWYWAMB9raUYukTyn8UbX68G6Mydy0ymrj0hvku_oVwQxzZ3F0Otv3IH_O5EKWsFJ8MsnzeYGuGW9ozemNyD7u0PTHSmwMEuLA_CrlfMJ2YfcFjIW0WnPRfpRc4fwpdn8krDHy10REUmQKBrdE8aPa9k-mgx5I/s4080/PXL_20231123_031309020.MP.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqb8QDcxta65eajG7TrOpCnoiDmV3tl2QWYWAMB9raUYukTyn8UbX68G6Mydy0ymrj0hvku_oVwQxzZ3F0Otv3IH_O5EKWsFJ8MsnzeYGuGW9ozemNyD7u0PTHSmwMEuLA_CrlfMJ2YfcFjIW0WnPRfpRc4fwpdn8krDHy10REUmQKBrdE8aPa9k-mgx5I/w200-h151/PXL_20231123_031309020.MP.jpg" width="200" /></a></b></div><b><br />Vignesh Vishwanath</b> ran the first session "Migrating from Platform DXP to Composable DXP". He started by asking for a show of hands for which version(s) of Sitecore you're currently working on. For the 10.x series there was a fairly even split of the room, by the time we got to 9.2 there were only a couple of hands, but the big surprise was the few hands for 8.x, 7.x, and 6.x!! Obviously some happy (or scared) customers to still be running those ancient versions. He assured everyone that XP wasn't going away, and that 10.4 would be released Q4 of 2024, and that obviously all the new composable stack was still very much compatible with the platform (XM/XP) products.<br />He then ran us through a couple of products which the Sitecore team are working on to assist the migration from platform XP ("the monolith") to composable: <p></p><p></p><ol style="text-align: left;"><li>xDB -> CDP (+ eventually Send) migration for Sitecore 9.0+<br />If you have XP + CDP even though this uses Connect it would come at no additional cost.<br />1million users (with a couple of custom facets) migrated in ~9hrs<br />They're currently looking for pilot projects, if you'd like to be involved!<br /><br /></li><li>XM on-prem content + users (10.1+) -> XM Cloud migration<br />GUI based<br />Select your content items and/or users and the tool handles the rest<br />No size limits</li></ol><div>Very exciting stuff! No doubt will be incredibly useful for many clients.<br /></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg859zXgq9PFGeoI0wH9lkzbwzFOB10akJ1Q1C4jZjiwig_KaQZ9n0OPGo40JJfmvxkPSYn742eJ8bxYn4XE1yfr1t38d2XM8sUAvFx7nf10TDnrA06rtZTcIwHkzGnO9TgGDRctdMTLyJQtrAgvqe6nJluceQBwg47wCeJYIICx27L3KdjDpuYg3S7yXzZ/s1782/PXL_20231123_035756235.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1341" data-original-width="1782" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg859zXgq9PFGeoI0wH9lkzbwzFOB10akJ1Q1C4jZjiwig_KaQZ9n0OPGo40JJfmvxkPSYn742eJ8bxYn4XE1yfr1t38d2XM8sUAvFx7nf10TDnrA06rtZTcIwHkzGnO9TgGDRctdMTLyJQtrAgvqe6nJluceQBwg47wCeJYIICx27L3KdjDpuYg3S7yXzZ/w200-h151/PXL_20231123_035756235.jpg" width="200" /></a></div><br />Next <b><a href="https://www.linkedin.com/in/grbaxter/" rel="nofollow" target="_blank">Greg Baxter</a></b> ran a session on "Which Sitecore Personalisation Tool is Right for You?" where he took us all (in great detail, as he describe it: "a rabbit hole") through different ways to personalise depending on "where you are at, and where you want to go". XP is still very robust and reliable, but if you haven't yet started on the journey then it's best to go with XM Cloud (to start with) and/or Personalize (for those who need more); the future is clearly the latter options.</div>
<div style="clear: both;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL2EvLw_KjnL09bsVlPTCmB18wk7Ll6-hbKDKxG9TmlO5JScc5Asw57SF82MaN7zvjNK9zojp_vxrsorNrfWVIJX9gtZ1-Df9C64KTcLKi7R_OrqTJMHapoOQDFSb3gSMspmUPNQ_uK2NFxucmVFmPab4BMm0jV_j0Ne4JSSy7CMGeYugowsfy2UBpUJUc/s2170/PXL_20231123_045509511.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1634" data-original-width="2170" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL2EvLw_KjnL09bsVlPTCmB18wk7Ll6-hbKDKxG9TmlO5JScc5Asw57SF82MaN7zvjNK9zojp_vxrsorNrfWVIJX9gtZ1-Df9C64KTcLKi7R_OrqTJMHapoOQDFSb3gSMspmUPNQ_uK2NFxucmVFmPab4BMm0jV_j0Ne4JSSy7CMGeYugowsfy2UBpUJUc/w200-h151/PXL_20231123_045509511.jpg" width="200" /></a></div><br />The <b><a href="https://www.dataweavers.com/" rel="nofollow" target="_blank">Dataweavers</a></b> team, in their quick sponsored session, took us through how - now that there are so many Sitecore products to deal with at the same time - it's important to have good DevOps, PerfOps, and SecOps. Naturally they wrapped with their plug: that they have the experience and tools to do this efficiently and effectively for any clients.</div><div><br /></div><div>After lunch we needed to select from a couple of different tracks: developer or marketer. This first day I went full developer.<br /><br /></div><div><b><a href="https://www.linkedin.com/in/vincent-lui-2188661/" rel="nofollow" target="_blank"></a><div class="separator" style="clear: both; text-align: center;"><a href="https://www.linkedin.com/in/vincent-lui-2188661/" rel="nofollow" target="_blank"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLb-bYGdYvHdqyxaGrBdHJzh9EodieYCSWWy852qa0uLGWHcUT4X7L4mTD5B3J4d9KwRzQXQcck2HIDfEEsypKPCB1Ksf7AGkLMcNRdx7bUe0Ye6Hz_hdVZkDEF6VJP0jxWm-OwKHiTuMUebzhDJ0diYe5WVE1KeVaJuLWv0CchRyzS5Ex3Po0YQVTcBcn/s2581/PXL_20231123_054425992.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1943" data-original-width="2581" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLb-bYGdYvHdqyxaGrBdHJzh9EodieYCSWWy852qa0uLGWHcUT4X7L4mTD5B3J4d9KwRzQXQcck2HIDfEEsypKPCB1Ksf7AGkLMcNRdx7bUe0Ye6Hz_hdVZkDEF6VJP0jxWm-OwKHiTuMUebzhDJ0diYe5WVE1KeVaJuLWv0CchRyzS5Ex3Po0YQVTcBcn/w200-h151/PXL_20231123_054425992.jpg" width="200" /></a></div><br />Vincent Lui</b> from CPA was up first with a session on "Abstracting Personalisation" where he described a bit about what CPA does and how it benefits its members. He went through a bunch of the tech that CPA uses to ensure that they collect as much data as they need to personalise for their users using a couple of different tools - CDP and EDP - and how these differ but complement each other. Naturally given the PII being collected there are a great number of tools and processes that are in use to protect the data, as well as restrict how it's used.</div><div><br /></div><div><b><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8Dupoa6nA8dZQKOeAdLoFB5qJqTgMCWWziPAXkmFHjd0m4xQz0WdhGL58bOydaV7reJjVRoTm7tyzFXCfdAlflSlgmcGhhgk4OR-9FiyzE9cnEATKkzCGNb73-ztlhGptd2p_F7BXn8bB4Tpry4WTzR96x6d1FfiU4EksmnM8F-zgxuyJ-Q6Sbv5WfyYg/s2447/PXL_20231123_063416943.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1842" data-original-width="2447" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8Dupoa6nA8dZQKOeAdLoFB5qJqTgMCWWziPAXkmFHjd0m4xQz0WdhGL58bOydaV7reJjVRoTm7tyzFXCfdAlflSlgmcGhhgk4OR-9FiyzE9cnEATKkzCGNb73-ztlhGptd2p_F7BXn8bB4Tpry4WTzR96x6d1FfiU4EksmnM8F-zgxuyJ-Q6Sbv5WfyYg/w200-h151/PXL_20231123_063416943.jpg" width="200" /></a></div><br />Yevgen Spektor</b> then followed with a session on "Estimating the upgrade from monolith DXP to XM Cloud" which covered how XM Cloud / headless allows for better separation of concerns, faster builds and deploys, and takes load off Sitecore (ie no CD servers). Key considerations for the move include: serialization (eg. Sitecore content serialization) that most Sitecore modules no longer work, personalisation needs to be refactored, there are no longer custom Solr indexes, there will be little-to-no custom (.NET) code, you need to use Docker, and you'll probably use webhooks. The team structure will also be largely focused on the frontend devs, with little to no backend. Definitely some things to keep in mind there!</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR5hax71gGsSrTR1qeJQnllR8zxFAajsGoEnRazfrandrOtvW5aT9Ib7DYoSkDMDgoIfOR_uW6KeKDN5gxBJbD944yqqdcs2YmduxlYUihBZxHLIKa8VkULn6oXh_qW4bNiEhTUbG7fdQj67kS7_YOgqB92SNgIQwDgWaNdst6MZENkIdtyj69ItT1TtGl/s4080/PXL_20231123_072315092.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR5hax71gGsSrTR1qeJQnllR8zxFAajsGoEnRazfrandrOtvW5aT9Ib7DYoSkDMDgoIfOR_uW6KeKDN5gxBJbD944yqqdcs2YmduxlYUihBZxHLIKa8VkULn6oXh_qW4bNiEhTUbG7fdQj67kS7_YOgqB92SNgIQwDgWaNdst6MZENkIdtyj69ItT1TtGl/w200-h151/PXL_20231123_072315092.jpg" width="200" /></a></div>Next <b><a href="https://www.linkedin.com/in/navneetpisharodi/" rel="nofollow" target="_blank">Navneet Pisharodi</a></b>'s session "Exploring a Modern Vercel Frontend for XM Cloud? An Architectural Viewpoint." took us through hosting a headless app in Vercel. Not only are they the creators of Next.JS (which you're very likely to be using to build your Sitecore head) they are easy to use with many efficiency gains like <i>gitops</i> (also allowing for immutable deployments and instant rollbacks) and many easy integrations (logging to SumoLogic being the example provided). Not to forget, Vercel is a partner of Sitecore meaning that (fingers crossed) any new developments on both sides will quickly be integrated into the other party's product(s). Licensing and costs were the final key consideration called out - pretty much always the case, but in this particular scenario if you're not familiar with headless hosting it can be something which may catch you out, or be difficult to estimate.</div><div><br /></div><div>To end the day, <b><a href="https://www.linkedin.com/in/gusq/" rel="nofollow" target="_blank">Gus</a></b> once again took the stage to award the Ultimate Experience award to <a href="https://www.destinationgoldcoast.com/" rel="nofollow" target="_blank">Destination Gold Coast</a> and their partner <a href="https://www.ping-works.com.au/" rel="nofollow" target="_blank">PING</a> for their excellent use of Sitecore.<br /></div><div><br /></div><div><br /></div><div><h3 style="text-align: left;">Day 2 (Friday)</h3></div><div><b><a href="https://www.linkedin.com/in/sanchias/" rel="nofollow" target="_blank">Sanchia Stafford-Gaffney</a></b> and <b><a href="https://www.linkedin.com/in/troyouttram/" rel="nofollow" target="_blank">Troy Outtram</a></b> from Deloitte kicked off day 2 by reminding us that we in the room are the fortunate few, and there are many people globally struggling in many different ways. Deloitte has started a <a href="https://www.deloitte.com/au/en/services/consulting/case-studies/new-diverse-workforce-boosts-technology-jobs.html" rel="nofollow" target="_blank">Digital Career Compass</a> to help those struggling to find jobs in digital, mentored and trained by people like us and potentially going on to be employed by companies such as Sitecore (as well as a bunch more they also listed). Not only do these candidates come from a vast array of backgrounds bringing with them a wide variety of experience but they are generally super driven and excellent employees.<br /><br /></div><div>Following this we then split back into Devloper and Marketer streams once more, where I attended:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK5_oAWCKZ9UvbNZORDjmGyK5DGz9gEXhMRbaIDkFB_7N6YC9_Emqg0IoNrybv62a1BANJGFa9-YuOlQzKCg2fTBkZa0uhDVxaQqjuFBJ0ywDcAvywIN1ZggjlenaG3P_dcW0fgtO3dhMi1oZ6fuZb-0j_dDXnCPdwzgXvOa7BRUyyeazkAOBhJ2oWVkes/s4080/PXL_20231124_003103444.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK5_oAWCKZ9UvbNZORDjmGyK5DGz9gEXhMRbaIDkFB_7N6YC9_Emqg0IoNrybv62a1BANJGFa9-YuOlQzKCg2fTBkZa0uhDVxaQqjuFBJ0ywDcAvywIN1ZggjlenaG3P_dcW0fgtO3dhMi1oZ6fuZb-0j_dDXnCPdwzgXvOa7BRUyyeazkAOBhJ2oWVkes/w200-h151/PXL_20231124_003103444.jpg" width="200" /></a></div><br />"From Frustration to Success: Marketers' Guide to Sitecore XM Cloud Do's and Don'ts" by <a href="https://www.linkedin.com/in/raman-gupta/" rel="nofollow" target="_blank"><b>Raman Gupta</b></a> and <a href="https://www.linkedin.com/in/vikaskumarg/" rel="nofollow" target="_blank">Vikas Kumar</a> was an excellent walk through their learnings from experiences implementing XM Cloud. Not only a great list of "dos" and "don'ts" (especially for those of us who haven't yet had the chance to work on a full XM Cloud implementation) but they also treated us to an excellent demo of XM Cloud and the component builder. If you'd like to download a copy of their list you can <a href="https://www.espire.com/ebook/sitecore-xm-cloud-dos-and-donts?source=QRCode&medium=email&campaign=ebookXMclouddosanddonts" rel="nofollow" target="_blank">fill out the form on their website to get a free copy</a>.<br /></div><div></div><div></div><div></div><div><br /><b><a href="https://www.linkedin.com/in/rajeshvsure/" rel="nofollow" target="_blank"></a><div class="separator" style="clear: both; text-align: center;"><a href="https://www.linkedin.com/in/rajeshvsure/" rel="nofollow" target="_blank"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWvJSGV3XvELBOCWZwwY1UrhUcyVAL-sQOFJ3KN3vY2xt4Yt8Jhyphenhyphen2Ri_91oUEReqZyw9ulx2LjwVG61gN8jdRMNEuQMHbMAeI3aMwVVkM50jLkd48lnjjvufbEhuAb91KfR-PM7fYQ0E6DzX-LKJWeo1RvOiqWzsofC0C8ONKXg6uyfTpJhVeaCIN7imVE/s4080/PXL_20231124_012326952.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWvJSGV3XvELBOCWZwwY1UrhUcyVAL-sQOFJ3KN3vY2xt4Yt8Jhyphenhyphen2Ri_91oUEReqZyw9ulx2LjwVG61gN8jdRMNEuQMHbMAeI3aMwVVkM50jLkd48lnjjvufbEhuAb91KfR-PM7fYQ0E6DzX-LKJWeo1RvOiqWzsofC0C8ONKXg6uyfTpJhVeaCIN7imVE/w200-h151/PXL_20231124_012326952.jpg" width="200" /></a></div><br />Rajesh Sure</b> then took us through a bit of a deep dive into webhooks in his session "Unlocking Seamless Connections: Leveraging Sitecore Webhooks for Enhanced Integrations". Not only did he cover the various types of webhooks and how they are used (a couple of key considerations are: timeouts, and the fact that there is no retry logic), but he also had a great demo of a webhook triggering a message to Slack and email, for which he walked us through the code. Super useful stuff!<br /></div><div><br /></div><div><b><a href="https://www.linkedin.com/in/iamandycohen/" rel="nofollow" target="_blank"></a><div class="separator" style="clear: both; text-align: center;"><a href="https://www.linkedin.com/in/iamandycohen/" rel="nofollow" target="_blank"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFFU6rtelX8ojluEej5mmgY4uV-fS5u98JutMxJcPj1tikd9pBalhlJh8TQ4bztIxqt0v-9FMr_o6z8brQdBXmv8DrcNpwz34tgdKEKQ6LAw-CKRxUPcAQPuAEdGqG42BJ2-s9el7v78vpXIq8opg8Jnr9UDCEuv5DaTORlOwY6cnv8awR2Ioguuyv4gui/s4080/PXL_20231124_030223667.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFFU6rtelX8ojluEej5mmgY4uV-fS5u98JutMxJcPj1tikd9pBalhlJh8TQ4bztIxqt0v-9FMr_o6z8brQdBXmv8DrcNpwz34tgdKEKQ6LAw-CKRxUPcAQPuAEdGqG42BJ2-s9el7v78vpXIq8opg8Jnr9UDCEuv5DaTORlOwY6cnv8awR2Ioguuyv4gui/w200-h151/PXL_20231124_030223667.jpg" width="200" /></a></div><br />Andy Cohen</b> from the Sitecore product team followed up with a peek under the hood of some Sitecore tech (XM Cloud in particular) in his session "Building for Resiliency" where he explained how Sitecore uses event-driven architectures, liveness checks (with tools such as <a href="https://kafka.apache.org/" rel="nofollow" target="_blank">Apache Kafka</a> and <a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/overview" rel="nofollow" target="_blank">Azure App Configuration</a>) to ensure that Sitecore can handle all sorts of "outage" scenarios. Not only that but he suggested a few books around event-driven architecture and domain-driven design that I'll need to take a look at some time!</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNHDEztsZrdCKpOuy0QmsDk8cOtURnvfwKk4W-90y8jR8YnTKA9aTbr1IzQLJDez22VOlbc0EyzrMrug4iuJJGMRd-9B9mouOnan_tiAWmc-O4TRRyXTe_EEW86QgX5ob00kLj6XPYM9BLZ33RmfjMOOAfH6Ktb5X8V8HNKP2AIZSFsn7rF6dYGtJvnQfa/s4080/PXL_20231124_035402770.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNHDEztsZrdCKpOuy0QmsDk8cOtURnvfwKk4W-90y8jR8YnTKA9aTbr1IzQLJDez22VOlbc0EyzrMrug4iuJJGMRd-9B9mouOnan_tiAWmc-O4TRRyXTe_EEW86QgX5ob00kLj6XPYM9BLZ33RmfjMOOAfH6Ktb5X8V8HNKP2AIZSFsn7rF6dYGtJvnQfa/w200-h151/PXL_20231124_035402770.jpg" width="200" /></a></div>vNext up I went to the session "Exploring Atomic Design with Storybook in Next.js for Sitecore Headless" presented in part by an old teammate of mine <b><a href="https://www.linkedin.com/in/mdarifuzzaman/" rel="nofollow" target="_blank">Arif Uzzaman</a></b>, as well as his client <a href="https://www.linkedin.com/in/nickunjsanghadia/" rel="nofollow" target="_blank">Nikunj Sanghadia</a>. In this breakout they took us through how frontend developers barely (if at all) need to know Sitecore, and can utilise atomic design to ensure consistent design between components, as well as Storybook to preview and showcase their output. Not only that but it's easy to produce components which look great and play well in Sitecore. As someone who uses Storybook extensively (but not atomic design at this time) this was quite familiar, and there were some great (and funny) questions from others in the room going through a similar journey.<br /><br /></div><div><b><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRR7saYYT1MSkMI0zVQyxr6v1xG9rLFuIQMZ0hAM-R-UCnY9j5KuRuA9VAZPxpJ6etexIZ-z7WuC_61F9YQtC70kDjJhfrgFiK8uaVJnD1ccwOkvC97cxnuL17BQraJM14-Q3qMmJdID4guXByhPkhdcBfHhUxyup_Jh_OGCbr2IXoeGQnWutR_tqY3UrM/s4080/PXL_20231124_045428118.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRR7saYYT1MSkMI0zVQyxr6v1xG9rLFuIQMZ0hAM-R-UCnY9j5KuRuA9VAZPxpJ6etexIZ-z7WuC_61F9YQtC70kDjJhfrgFiK8uaVJnD1ccwOkvC97cxnuL17BQraJM14-Q3qMmJdID4guXByhPkhdcBfHhUxyup_Jh_OGCbr2IXoeGQnWutR_tqY3UrM/w200-h151/PXL_20231124_045428118.jpg" width="200" /></a></div><br />Alistair Deneys</b> from the Sitecore product team, in his session "Exploring the Experience Edge GraphQL Schema", walked us through the different GraphQL schemas for XM/XM Cloud and ContentHub / ContentHub One and various ways these could be used for different technical requirements. Not only that but he presented it all on his custom-built <a href="https://learn.microsoft.com/en-us/dotnet/maui/what-is-maui?view=net-maui-8.0" rel="nofollow" target="_blank">.NET MAUI</a> interface consuming content from ContentHub One. Super cool!<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir8dukDlNPb3pIhAebSTUa2UhiwUTIfdl8QUODjETkc_iU_-ifRkVnX0KnC_XpG3rJI2iX57AWgDxTo34KAo2xmPFgAkyI68bFVKSie_hrnQ40LFyYjXw2RVX4jpPIPG-ReETjc2rcPQoZLnTSYUMCOapV37jfytUSplRrEhEcP716HwKaZHszrI_QZhEu/s4080/PXL_20231124_060803556.jpg" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;">
<img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir8dukDlNPb3pIhAebSTUa2UhiwUTIfdl8QUODjETkc_iU_-ifRkVnX0KnC_XpG3rJI2iX57AWgDxTo34KAo2xmPFgAkyI68bFVKSie_hrnQ40LFyYjXw2RVX4jpPIPG-ReETjc2rcPQoZLnTSYUMCOapV37jfytUSplRrEhEcP716HwKaZHszrI_QZhEu/w200-h151/PXL_20231124_060803556.jpg" width="200" /></a>
</div>The final sessions included <b><a href="https://www.linkedin.com/in/arul-pushpam-murugan/" rel="nofollow" target="_blank">Arul Pushpam Murugan</a></b> who gave an inspiring talk "Empowering Women in Tech: A Journey of Growth with Sitecore" where she took us through some of the hurdles she herself had to overcome, as well as tips for other women in the industry.<br />
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkvCpq0B1yYm4E1ZqEjJv6EgYwXJhBPFp-m_xupBVPgbSWX-nesEXmLZzdHWdoY49A4FxdWjRUokiXp9IcWja8dBHNQqzo5IdMLkWVX0dJIR9RtLFcVIPjvWW_kZF5Xq7qY_1KuSEDkyzOkPQm3TOKa91pEdWf6_9uy3qtOe7NDKszpYjFg4_SoS5rKeEO/s4080/PXL_20231124_063134171.jpg" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3072" data-original-width="4080" height="151" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkvCpq0B1yYm4E1ZqEjJv6EgYwXJhBPFp-m_xupBVPgbSWX-nesEXmLZzdHWdoY49A4FxdWjRUokiXp9IcWja8dBHNQqzo5IdMLkWVX0dJIR9RtLFcVIPjvWW_kZF5Xq7qY_1KuSEDkyzOkPQm3TOKa91pEdWf6_9uy3qtOe7NDKszpYjFg4_SoS5rKeEO/w200-h151/PXL_20231124_063134171.jpg" width="200" /></a></div>
<br /><br />
<b><a href="https://www.linkedin.com/in/sandy-drew-1796333/" rel="nofollow" target="_blank">Sandy Drew</a></b> then took us through the technical side of Toyota's XM Cloud build in his session "Driving hybrid success with Toyota Used Vehicles" which sounded like a great implementation, utilising Next.JS SSG and Discover.</div>
<div><h3 style="clear: both; text-align: left;">Final Thoughts</h3><div style="text-align: left;">Whether Sitecore's indeed at an "inflection point" (as Gus put it at the start) it has clearly matured even over the last year or so. It's clear that even though there are still a vast number of clients and partners nursing old Sitecore instances (many of which are no doubt out of support), there are also a really healthy number of companies who have not only made the leap to composible (or hybrid) but have completed implementations and come out unscathed, and ready to share their story. Sitecore is listening and learning from those first implementations, and the "new" SaaS products are continually improving. At this point, if you're considering Sitecore's new products, you're not an "early adopter" or "trailblazer" you're just in good company.<br /></div><div style="text-align: left;"><br /></div><div style="text-align: left;">It's great to come to community events like this where it's not all vendor-painted roses and sunshine - "our product is the greatest and can do everything". Here you'll find real stories and talk to real people who can reassure you of the reality - that even though not 100% of what you want/need to do will be a walk in the park, it's all achievable when we share our knowledge, experience, and love of developing with Sitecore.<br /></div></div><div><br /></div><p></p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-54818234796200497792023-11-14T14:37:00.004+11:002023-11-14T14:37:36.421+11:00SUGCON ANZ!Just booked my tickets to <a href="https://anz.sugcon.events/" rel="nofollow" target="_blank">SUGCON ANZ</a>! Looking forward to seeing some familiar faces, and hopefully meeting some new people as well. Many (if not all) of us are all introverted techies, but remember we're all there for the same reason; if nothing else try to say hi to the person sitting next to you :)<div><br /></div><div>There's a <a href="https://anz.sugcon.events/Agenda" rel="nofollow" target="_blank">great agenda</a> - it's certainly going to be tough choosing between some of those afternoon sessions. Don't forget that on the first day registration opens at 9am, but the real fun doesn't start until after midday. That should hopefully mean some reasonable flight times (and hopefully prices).</div><div><br /></div><div>Hope to see you there!</div>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-71392025010921805382023-10-08T14:23:00.009+11:002023-10-08T14:37:47.883+11:00Security & Permissions with headless Sitecore<p>Security is a big headline these days (and should always be in the forefront of anyone's mind) and when it comes to content permission and user authorization the Sitecore "monolith" has always provided these capabilities out-of-the-box; however with the giant shift to headless over the last couple of years (or more) and now the move to SaaS these have grown vastly more complicated than the simple out-of-the-box content-permission-setting capabilities we used to know and love.</p><h3 style="text-align: left;">The issue <br /></h3><p>So why is it that things are now more complicated? <br />With the move to headless and composable, we have more of a separation of concerns - in this case, separation of the authentication/authorization, the display (/render) of content, and the provision of content. This is most evident (technically, at least) in the use of REST and GraphQL by the headless code to retrieve content from the CMS (or DXP if you want to be fancy) where renderings were previously automatically provided their content by Sitecore. </p><p>The Sitecore "monolith" provides all 3 of these capabilities: </p><ul style="text-align: left;"><li>Auth* through old ASP.NET users/roles, <a href="https://doc.sitecore.com/xp/en/developers/102/sitecore-experience-manager/using-federated-authentication-with-sitecore.html" rel="nofollow" target="_blank">Federated Authentication</a>, or <a href="https://doc.sitecore.com/xp/en/developers/102/sitecore-experience-manager/use-the-sitecore-identity-server-as-a-federation-gateway.html" rel="nofollow" target="_blank">Sitecore Identity</a> </li><ul><li>Authenticates and assigns roles to user for authorization</li></ul><li>Display/render of content through .NET MVC controller/view renderings (let's not talk about webforms or XSLT)</li><ul><li><b>Authorizes user by checking permissions set on content in Sitecore XM </b></li></ul><li>Provision of content through (amongst other things) datasources provided and consumed by the renderings </li></ul><p>In the new headless/composable world, these would be:</p><ul style="text-align: left;"><li>Auth though a separate identity provider (IDP) such as Okta / Auth0 / OneLogin / MS Entra</li><ul><li>Authenticates and assigns roles to user for authorization<br /></li></ul><li>Display/render of content through headless SDKs such as Next.js (using REST / GraphQL)<br /></li><li>Provision of content from Sitecore XM / <a href="https://www.sitecore.com/products/content-hub" rel="nofollow" target="_blank">Content Hub</a> / <a href="https://www.sitecore.com/products/content-hub-one" rel="nofollow" target="_blank">Content Hub One</a> <br /></li></ul><p>Notice something missing in the second list? Hopefully you did, as it's highlighted in bold in the first list! <br />There is now a disconnect between the roles assigned to the user by the IDP, and any roles you create in Sitecore / the permissions set on content.</p><p>This disconnect has no doubt been encountered by anyone working in a headless environment which requires their users to log in, and I have seen a few examples of how various people have thought about / tackled a solution. The following sections outline my approach.<br /></p><h3 style="text-align: left;">A solution</h3><div style="text-align: left;">There are 2 gaps mentioned above, and in case you missed them these are:</div><div style="text-align: left;"><ol style="text-align: left;"><li>A disconnect between roles assigned to a user by the IDP and roles in Sitecore (ie roles assigned to content)<br /></li><li>No out-of-the-box capabilities by the headless SDK to determine whether users are authorized to view content</li></ol><p>You might be thinking "but both the GraphQL and REST endpoints support authorisation!". This was my initial thought, and I spent quite a bit of time deep diving into whether this was a viable option (ie. calling the layout service / GraphQL using the authenticated user details from the headless code). Let me save you a lot of time and headache: short of a quick and dirty option (simple logged in / not logged in user content, for example by swapping which API key you use to call the layout service) this isn't going to be an option you can use for anything serious.<br /></p><p>I have seen other solutions which propose mapping the permissions (ie. roles <=> content) in a separate system, however my preference was to keep this within Sitecore. Sitecore XM still offers a flexible and robust ability to assign which content should be accessible by which users/roles, and building this again / finding a separate solution just seems like extra effort for no reason. I also personally do not feel like using this existing functionality goes against the spirit of composable at all. This mindset was the foundation for the remainder of the approach below.</p><p>I'm not going to dive too deeply in to the first point in the list above - suffice it to say there will need to be a way to ensure your roles in Sitecore match the roles assigned by your IDP. There are at least a couple of options:</p><ol style="text-align: left;"><li><a href="https://doc.sitecore.com/xp/en/developers/102/developer-tools/configure-role-serialization.html" rel="nofollow" target="_blank">Configure role serialization</a>, dynamically generate a yml file containing the roles, and dotnet sitecore ser push it</li><li>Create an API endpoint on your Sitecore CM which calls <a href="http://sitecorecodesamples.blogspot.com/2010/11/programmatically-create-sitecore-role.html" rel="nofollow" target="_blank">System.Web.Security.Roles.CreateRole()</a> <br /></li></ol></div><p>The meat of the dev work required, as far as I'm concerned, lies in exposing the roles and consuming them as part of authorization in the headless code.</p><h4 style="text-align: left;">Exposing the permissions</h4><p>After <a href="https://doc.sitecore.com/xp/en/developers/102/platform-administration-and-architecture/the-access-rights.html" rel="nofollow" target="_blank">setting the content (item) permissions</a> (an out-of-the-box Sitecore XM exercise), the first thing that needs to be done is exposing these permissions. Content is consumed by the headless code either via the layout service, or GraphQL, so these are the 2 scenarios that need to be covered.</p><p><b>GraphQL: </b></p><p>The quick and dirty way is to patch out the standard field filter, and add the security fields into your schema.<br/><b>Note: </b><i>this is not going to work if you're using Edge (/XM Cloud)</i>.</p>
<p>
<script src="https://gist.github.com/moo2u2/38a968d174366039219d5aced963f52e.js"></script></p>
<p>If you want a version which is compatible with Edge / XM Cloud, you'll want to create your own "faux security" field (you might call it "permissions"), and copy the standard (<code>__Security</code>) field value to this custom (permissions) field in a <code>item:saved</code> event handler. See <a href="https://sitecore.stackexchange.com/questions/15454/how-to-change-sitecore-field-value-based-on-another-fields" rel="nofollow" target="_blank">this stackexchange answer</a> for an example of a similar event handler.<br /></p><p><b>Layout service: </b></p><p><i><a href="https://github.com/moo2u2/Sitecore-Permissions/" rel="nofollow" target="_blank">See sample repo<b> <br /></b></a></i></p><p>The layout service filters out all the standard fields (including <code>__Security</code>) in the <code>FieldFilter</code> method of <code>JssItemSerializer</code> (or whichever item serializer your site is using) so we will need to <a href="https://github.com/moo2u2/Sitecore-Permissions/blob/main/SitecorePermissions/code/App_Config/Include/Foundation/Foundation.LayoutService.config" rel="nofollow" target="_blank">patch that out</a> and <a href="https://github.com/moo2u2/Sitecore-Permissions/blob/main/SitecorePermissions/code/LayoutService/ItemSerializers/SecurityJssItemSerializer.cs#L18" rel="nofollow" target="_blank">add our own filter</a>. <br />We then want to serialize our security field in a more readable format, which we can do in a custom <a href="https://github.com/moo2u2/Sitecore-Permissions/blob/main/SitecorePermissions/code/LayoutService/FieldSerializers/SecurityFieldSerializer.cs" rel="nofollow" target="_blank">SecurityFieldSerializer</a>.</p><p>After this is done we can see all the <code>__Security</code> fields in our layout service result!<br /></p><h4 style="text-align: left;">Authorization in Next.js<br /></h4><p>When it comes to Next.js, <i>authentication</i> really comes down to <a href="http://next-auth.js.org/" rel="nofollow" target="_blank">next-auth</a> which has a host of IDP plugins out of the box (or you can develop your own easily enough if you really need / want to). <i>Authorization</i>, however, is a custom exercise. Long story short: once the SDK has called the layout service, the headless code needs to parse the permissions and prevent the user from seeing the page, or hide the appropriate content on the page from the user. </p><p>Again, there are 2 parts to this: security at a page level (not authorized to view the page) or at a component level (hide a component from the user by removing it from the layout).</p><p>These can both be accomplished largely by <a href="https://gist.github.com/moo2u2/3212cf9139321d92c2b435f24da8d132" rel="nofollow" target="_blank">customising normal-mode.ts</a> (at least in Sitecore 10.2) with a <a href="https://gist.github.com/moo2u2/1b22f8a14086511e512d69e729df8731" rel="nofollow" target="_blank">helper to parse un-formatted security fields</a>.<br /></p>
<p><b>Page level (lines 68-74) and component-level (lines 77-80):</b></p>
<script src="https://gist.github.com/moo2u2/3212cf9139321d92c2b435f24da8d132.js#file-normal-mode-ts"></script>
<p>In the former we simply check the page security field, and in the latter we loop through all placeholders and check the security field set on the datasource. This does not necessarily cover 100% of your use cases (components without datasources for example) but the rest can certainly be implemented in a similar fashion.</p><h3 style="text-align: left;">Conclusion <br /></h3><p>Securing certain content - to be shown only for authorized users - has been a site requirement for almost as long as the web has been around. The advent of headless and/or composable mandates a new way of thinking and new approach to securing your site content.<br /></p><p>With the logic provided above you should be able to both cover the vast majority of your permissions cases, as well as have a solid foundation for custom use cases where permissions might be needed within each of your components.</p><p>If you've made it this far - firstly thanks for reading! - I hope you learned something new today, or maybe re-enforced something you already knew. I'd love to get your feedback either way, so please leave a comment below, whether it's in agreement or some constructive criticism!<br /></p><p><br /></p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-40779800760787116492023-08-22T09:08:00.003+10:002023-08-22T09:08:35.134+10:00502s Massive Headers and So Many Cookies<p>We recently came across an (initially very confusing) issue where, on our cloud environments, a couple of fairly inconspicuous - but consistent - pages were making the server return a status code 502, or 520. These environments had Cloudflare in front of them, which was showing us the 502 response, but our development environments (without Cloudflare) had no issues and the same pages and content loaded just fine.</p><p></p><blockquote>Now I'll preface this by saying it was certainly something we (now know we) were doing incorrectly, but for the sake of anyone else who happens to come across the same thing I thought I'd share the debugging, findings, and fix.</blockquote><p></p><p>So the natural first step to narrow down the cause of most things is to remove stuff from the page until it starts working again! By first removing components from the page(s), and then removing particuar portions/lines from those components, we found that it seemed to be next-auth related lines where we were fetching session info using useSession() (frontend) and/or getServerSession() (backend). At this point we were pretty stumped: these same calls were working just find on some pages, but on a couple of particular pages they seemed to be causing issues... time for a web search to see if we can at least narrow things down!</p><p>A quick search for "502" and "next-auth" brings up a few useful results, but in particular <a href="https://stackoverflow.com/a/75136826/910971" rel="nofollow" target="_blank">this stackoverflow answer</a> seemed to indicate it might be something to do with headers. At least we had somewhere to start! Hosting for this project was in AWS, with EC2 instances behind a load balancer, all of which sits behind Cloudflare. As Cloudflare is the front endpoint showing the error, the natural instinct is to pit it on something to do with Cloudflare. Hitting the endpoint from another server within the same network (bypassing Cloudflare) showed that the 502 was still returned. After that, the infra team provided us with the info we needed about header sizing (from the <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html" rel="nofollow" target="_blank">AWS load balancer limits</a>):</p><table id="w227aac21c27"><thead><tr><th>Name</th>
<th>Default</th>
<th>Adjustable</th>
</tr>
</thead>
<tbody><tr>
<td>Request line</td>
<td>16 K</td>
<td>No</td>
</tr>
<tr>
<td>Single header</td>
<td>16 K</td>
<td>No</td>
</tr>
<tr>
<td>Entire response header</td>
<td>32 K</td>
<td>No</td>
</tr>
<tr>
<td>Entire request header</td>
<td>64 K</td>
<td>No</td></tr></tbody></table><p>It didn't take long to find that this was indeed likely the issue - a quick look at the header size for one of the pages showed it was certainly above the limit. Next question was obviously: "why"? That's quite a hefty size for headers. Again, taking a look at the headers for one of the pages, it didn't take long to see the issue:</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF9IfcuUAebZl3PtZ4KsAf_8P7HgAv5JBMrLqGbaDuJuxEj9UFhcVueLoDrDzwm_WIf0mmYlQ07HUFYjStC3sonZWPFbQ8k0l4XkArpTWNaw2w_sq0Jgj35koyfXYDWHKN87UeMXSLO7xYsOQO1wk-43HXXUOZhgQoPpdJS0nD9hgPLxu9ilgIrhI_flK-/s1060/cookie_headers.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="590" data-original-width="1060" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF9IfcuUAebZl3PtZ4KsAf_8P7HgAv5JBMrLqGbaDuJuxEj9UFhcVueLoDrDzwm_WIf0mmYlQ07HUFYjStC3sonZWPFbQ8k0l4XkArpTWNaw2w_sq0Jgj35koyfXYDWHKN87UeMXSLO7xYsOQO1wk-43HXXUOZhgQoPpdJS0nD9hgPLxu9ilgIrhI_flK-/s16000/cookie_headers.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">and there was more to it than just what's shown here!<br /></td></tr></tbody></table><br /><p></p><p>Wow, that's a lot of (very big) cookies being set in the headers... a very big WTF was in order!</p><p>Now for those who aren't familiar, these cookies are set by <a href="https://next-auth.js.org/" rel="nofollow" target="_blank">next-auth</a> and split/chunked (session-token.0, session-token.1 etc.) <a href="https://next-auth.js.org/configuration/options#cookies" rel="nofollow" target="_blank">at 4kb by default</a>. That's fair enough, we expected to see a couple and potentially very large ones; certainly <u>not</u> up to a dozen or so.</p><h4 style="text-align: left;">The Realisation<br /></h4><p>What came next was a lot of debugging, stepping through our code, the next-auth code, and to skip to the end: the realisation that every time <code>getServerSession()</code> is called, a new cookie (chunked, so actually multiple cookies) is set!</p><p>In our case, we had a few Sitecore headless components which had their own <code>getServerSideProps()</code> (<a href="https://doc.sitecore.com/xp/en/developers/hd/20/sitecore-headless-development/component-level-data-fetching-in-jss-next-js-apps.html" rel="nofollow" target="_blank">component-level data fetching</a>) and inside this method each component was calling <code>getServerSession()</code> to get the session data it needed to fetch its data. Ergo for each component using the session, a new cookie was created, blowing out the header size (and potentially causing session issues as well).<br /></p><h4 style="text-align: left;">The Solution</h4><p>So we had pinpointed the issue - now what? These components needed the session info (eg. user's account ID) in order to fetch their data, this wasn't just something we could do without. </p><p>The key here is to understand how the component-level data fetching works: if you take a look at the [[...path]].tsx file which you get from a default instantiation of a Sitecore headless project, you will see inside it a getServerSideProps() function, and inside that a call to sitecorePagePropsFactory.create(). This is the starting point for the getServerSideProps() call of each component. </p><p>If you follow the code, you will see the SitecorePagePropsFactory in page-props-factory\index.ts loops through each "plugin" and executes it, using a context, and setting props. This context is the one which comes through in the getServerSideProps() function at the component level.</p>
<p>In [[...path]].tsx:</p>
<p>Using this knowledge, we can fetch our session in [[...path]].tsx, the starting point for all server-side logic, and pass it through in the "plugin context" to each component, so that we don't have to re-fetch it.</p>
<pre>const session = await getServerSession(context.req, context.res, authOptions);
await sitecorePagePropsFactory.create({ session, ...context});</pre>
<p>then in each component:</p>
<pre>export const getServerSideProps = async (_rendering, _layoutData, context) {
const fetched = fetchSomeData(context.session.userId);
}</pre>
<p>Hope this helps anyone else who comes across this issue (or something like it)!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-66228318213963427842023-07-23T20:09:00.008+10:002023-07-23T20:11:08.967+10:00Secure (HTTPS) Your Non-Prod Next.JS Headless Sites<blockquote><b>Preface:</b> this isn't something you'll want to want to use in production, but great for non-prod or local
environments!</blockquote>
<p>If you're running Sitecore on Windows servers (not Azure PaaS where you can simply get a <a href="https://learn.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=apex#create-a-free-managed-certificate" rel="nofollow" target="_blank">free managed certificate</a>), and want to re-use your infrastructure (eg. in a non-prod environment or
for testing purposes) you may choose to use that same Windows machine to run your headless app. This can save on
costs, energy that can be spent on development (or setting up production), and help just to keep things
simple.<br />Obviously there are heaps of hosting providers such as Vercel where you could run it, and if that's an option that works for you I would certainly recommend it. If not, read on...<br /></p>
<p>The simplest way would be to copy your code to the same server - in a new folder beside your Sitecore installation -
install Node and run: <code>npm start:production</code>. This will start your app on port 3000 (or whatever port you have
configured).</p>
<p>This is fine if you just want to preview your app, but if you want it to work inside of Experience Editor, and your
CM instance is using <b>https</b>, you'll need your app to also be served securely (or else you'll see a very broken
site!).</p>
<h3>Obtaining a Domain Name</h3><div style="text-align: left;">I'm not going to tell you how to go about getting a domain name. Presumably if you've got Sitecore up and running somewhere you know how to go about getting a domain name, or already have one (and/or can use a sub-domain). I'm a fan of <a href="https://www.namecheap.com/" rel="nofollow" target="_blank">Namecheap</a>, but whatever is cheapest/easiest!<br /></div><h3>Obtaining a Free Certificate</h3>
<p>Easiest way to get a free certificate (on pretty much any system) these days? <a href="https://letsencrypt.org/getting-started/" rel="nofollow" target="_blank">LetsEncrypt</a>!</p>
<ol>
<li>Download <a href="https://certbot.eff.org/instructions?ws=other&os=windows" rel="nofollow" target="_blank">CertBot</a> from the letsencrypt site</li>
<li>Install it</li>
<li>Temporarily stop your IIS server (if it's running) so that Certbot can use its own server<br /></li>
<li>Request a certificate by running:<br /><code>certbot certonly --standalone -d "*.your.domain.com"</code><br />(or any of
the other methods such as DNS validation)</li>
<li>Start your IIS (if you stopped it in #3 above)<br /></li>
</ol>
<h3>HTTPS Using the Certificate</h3>
<p>Since we're using Node anyway to run the headless app, we can make use of the <a href="https://www.npmjs.com/package/local-ssl-proxy" rel="nofollow" target="_blank"><span style="font-family: courier;">local-ssl-proxy</span></a> package to proxy your app through HTTPS with a single line!.</p>
<ol>
<li>Open a command prompt and run:<br /><code>npx local-ssl-proxy --key C:\Certbot\archive\your.domain.com\privkey1.pem --cert
C:\Certbot\archive\your.domain.com\cert1.pem --source 443 --target 3000</code><br />(obviously substitute your port
443 for anything else if it's occupied!)</li>
</ol>
<p>Simple as that! It's so easy there's no excuse not to be using HTTPS for non-prods (or even local!) :) <br /></p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-67019432818120809202023-06-17T14:21:00.003+10:002023-06-17T14:21:17.172+10:00Monitoring Azure Web Jobs Availability<p>...and now for something non-Sitecore related!</p><p>I help run a friend's small family business website in Azure where we use <a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2016/june/azure-app-services-using-azure-app-services-to-convert-a-web-page-to-pdf#azure-webjob" rel="nofollow" target="_blank">PDF generation through an Azure Web Job</a> which runs on the same App Service Plan as their website (to save on costs).</p><p>Recently we've been seeing issues where the Web Job decides to suddenly stop, and I have to manually restart it - easy enough, but very frustrating (and they have to ping me to tell me when they notice). While I look into the cause of the issues I thought it was worth setting up some alerts so that I can be a bit proactive and start the Web Job before they notice (if possible).</p><p>You might think that this is a simple case of setting up a standard Alert in Azure Monitor, but Web Jobs work differently (effectively, behind-the-scenes) and there is no easy way to do this out of the box. </p><p>Fortunately, there are a few blogs around such <a href="https://blog.kloud.com.au/2016/08/11/monitoring-azure-web-jobs-health-with-application-insights/">Monitoring Azure WebJobs Health with Application Insights – Kloud Blog</a>, which can show you the way. Unfortunately most, if not all, suggest an old method of using Visual Studio to record a test rather than using some of the newer functionality of App Insights which supports headers. Now it's a nice and simple process!<br /></p><p>So without further ado, the steps:</p><ol style="text-align: left;"><li>From the Azure Portal, download your publishing profile - containing the username and password to access your Kudu environment, including deployments and APIs used to fetch Web Job status<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1BEHkDBv96VpAvHe9IN2OE2utZwd2kS7ySn1Ap1PLR2vgoVKNPzWVwLbpjp4RZvhpfL8T9qTWMcKhdd32_oK6VTb3fvRlrVlhVv3YBQZZ2xap1lWXZCn-mwAc8avJySPwFx-suBhQm8fNbcshaGcjQZem3CLpbh2AZktpSW6U-9rbcjTnvTdv_I8thQ/s1457/download-publish-profile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="153" data-original-width="1457" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1BEHkDBv96VpAvHe9IN2OE2utZwd2kS7ySn1Ap1PLR2vgoVKNPzWVwLbpjp4RZvhpfL8T9qTWMcKhdd32_oK6VTb3fvRlrVlhVv3YBQZZ2xap1lWXZCn-mwAc8avJySPwFx-suBhQm8fNbcshaGcjQZem3CLpbh2AZktpSW6U-9rbcjTnvTdv_I8thQ/s16000/download-publish-profile.png" /></a></div><br /></li><li>Calculate the Kudu API endpoint where you can see the status of your Web Job<br /><code>https://<span style="color: #ffa400;">your-web-app</span>.scm.azurewebsites.net/api/<span style="color: #ffa400;">triggered</span>webjobs/<span style="color: #ffa400;">YourWebJob</span></code></li><li>Use Postman or a tool like <a href="https://mixedanalytics.com/tools/basic-authentication-generator/" rel="nofollow" target="_blank">Basic Authentication Generator</a> to calculate the base64 encoded username + password to use in your <code>Authorization</code> header. <br />You should be able to call the endpoint in Postman and see an example <br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLC9nbQ66Bg12e303O3VFJSpB2du7B5zjoqzfMnP5GhJReWMb_T403Xp_bTqel2l0qYdo6laF16PIyK6f-00qogEE4Vl0sQv9wUijZILbp65QY3DkLxpgL0T5I3tGV4y-0WH9sWykxBbXgmWTQlrQ6fnEkF7meEy61y7umrZYGJMnehK_GwtyKRKtfuA/s1116/postman.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="881" data-original-width="1116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLC9nbQ66Bg12e303O3VFJSpB2du7B5zjoqzfMnP5GhJReWMb_T403Xp_bTqel2l0qYdo6laF16PIyK6f-00qogEE4Vl0sQv9wUijZILbp65QY3DkLxpgL0T5I3tGV4y-0WH9sWykxBbXgmWTQlrQ6fnEkF7meEy61y7umrZYGJMnehK_GwtyKRKtfuA/s16000/postman.png" /></a></div><br /></li><li>Create an availability check in App Insights which calls your endpoint with the Authorization header and checks for a status of "Running"<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9C4BlKMr8di9J55k550DTUP8UQI3tJqT0DvLIMDJqtVCaK9CQEt7HlJqvum-Hb5YB1zw2pa-K_7QvnmcBQck996V3_H0jhcGXNErM0U0qxmQjFqSCy4LAqbnreRNgn8x8_XiGUCgI2wnoBrxqWzLdxgSDwJqZC-uz7UECEpNEA-Y-K0vvESPzcdjRKw/s1298/availability.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1298" data-original-width="584" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9C4BlKMr8di9J55k550DTUP8UQI3tJqT0DvLIMDJqtVCaK9CQEt7HlJqvum-Hb5YB1zw2pa-K_7QvnmcBQck996V3_H0jhcGXNErM0U0qxmQjFqSCy4LAqbnreRNgn8x8_XiGUCgI2wnoBrxqWzLdxgSDwJqZC-uz7UECEpNEA-Y-K0vvESPzcdjRKw/s16000/availability.png" /></a></div><br /></li><li>Create an Alert in Azure Monitor which alerts you if the health check fails<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin5OBTXI2Co5aCstX5y-mgcb98SgGD6q65LbB50UfC6IMeb4_Qwrf88ViiFhd_X8wlOeok02bj7qMkdx1tv-vujxzYgN59n-P4LEJCNQj2E-3Vbs4a6Z-TAc0Fv1r6PG60t8VXyk-WgHakijlchg3BY2uyWol7YnNV52FZgUrEfTvrhZluBefc4jrzjQ/s893/alert.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="893" data-original-width="785" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin5OBTXI2Co5aCstX5y-mgcb98SgGD6q65LbB50UfC6IMeb4_Qwrf88ViiFhd_X8wlOeok02bj7qMkdx1tv-vujxzYgN59n-P4LEJCNQj2E-3Vbs4a6Z-TAc0Fv1r6PG60t8VXyk-WgHakijlchg3BY2uyWol7YnNV52FZgUrEfTvrhZluBefc4jrzjQ/s16000/alert.png" /></a></div><br /></li></ol>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-76956949244465928342023-05-10T21:27:00.009+10:002023-05-10T21:27:57.461+10:00Locking down requests from Sitecore to Head<p>For those coming from on-prem or PaaS the leap to the exciting new world of Sitecore SaaS can be quite daunting, with many new factors and considerations - not the least of which is security (which should always be front-of-mind). Your dev teams may be busy brushing up on containers and headless, but infra teams will be more concerned with integrating with XM Cloud, most notably with the Head.</p>
<p>Most of you will (hopefully) already be familiar with a couple of the ways that communication between the Head and Sitecore can be secured:</p>
<ol>
<li>A <a href="https://doc.sitecore.com/xp/en/developers/hd/211/sitecore-headless-development/create-a-sitecore-api-key.html" rel="nofollow" target="_blank">Sitecore API key</a> - allows you to lock down your API calls to the Layout Service and GraphQL endpoints, as well as impersonate users.</li>
<li>The <a href="https://doc.sitecore.com/xp/en/developers/hd/211/sitecore-headless-development/walkthrough--connecting-a-next-js-jss-app-to-sitecore-editors.html#secure-the-sitecore-editor-endpoint" rel="nofollow" target="_blank">JSS Editing Secret</a> - used as a shared key to ensure the app and Sitecore Editor are the only parties authorized to talk to one another while in Experience Editor mode.</li>
</ol>
<p>But what if you want a bit of extra security? Say you have a WAF (something to that effect) which only allows certain headers? As always, Sitecore is nice and extensible:</p>
<p>The class making the HTTP call to your Head app is <code>Sitecore.JavaScriptServices.ViewEngine.Http.RenderEngine</code> and it creates a <code>HttpClient</code> to make the requests using <code>Sitecore.JavaScriptServices.ViewEngine.Http.HttpClientFactory</code> which has the following method:</p>
<pre class="brush: csharp">public IHttpClient Create(HttpRenderEngineOptions options)
{
Assert.IsNotNull(options, "options");
return new TimeoutCapableWebClient(options.RequestTimeoutMs)
{
Encoding = Encoding.UTF8,
Headers = { [HttpRequestHeader.ContentType] = "application/json" }
};
}</pre>
<p>This <code>HttpClientFactory</code> is created through DI, so you can just inject your own and add your own headers! Too easy!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-65702243944461343622023-05-04T12:31:00.005+10:002023-05-04T12:31:58.513+10:00Sitecore Headless Forms - Hidden Field Using Query Value<p>This one goes out to @AJS on Sitecore slack who asked how they could implement the query string provider in Sitecore Forms (headless/JSS).</p><p>Firstly, make sure you add the <a href="https://github.com/bartverdonck/Sitecore-Forms-Extensions/blob/master/src/Feature/FormsExtensions/code/ValueProviders/QueryStringValueProvider.cs" rel="nofollow" target="_blank">QueryStringProvider</a> from Sitecore Forms Extensions, as well adding the <a href="https://github.com/bartverdonck/Sitecore-Forms-Extensions/blob/master/src/Feature/FormsExtensions/items/ValueProviders/Value%20Providers/URL%20Parameter%20-%20Query%20String.yml" rel="nofollow" target="_blank">required Sitecore item(s)</a> to your instance.</p><p>Once that's done see the <a href="https://doc.sitecore.com/xp/en/developers/hd/201/sitecore-headless-development/jss-forms-for-react.html" target="_blank">Sitecore documentation on adding a React/Next.JS form to your headless app</a>.</p><p>Finally you're ready to implement a custom field factory (see <a href="https://sitecoreandmore.blogspot.com/2023/03/custom-form-elements-in-headless-jss.html" rel="" target="_blank">my previous post</a>) with a custom hidden element, setting the value based on the value from the query string:</p><p><script src="https://gist.github.com/moo2u2/eeaa8b61ef714837f4312b273d1832c3.js"></script></p><p>Enjoy!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-24075441443529311612023-04-28T15:06:00.013+10:002023-05-01T09:12:18.288+10:00Sitecore JSS/Headless ColumnSplitter in 10.2 (with bonus Tailwind styles)<p>I saw a post in the Sitecore Slack channel recently asking about ColumnSplitter support in 10.2. This was something I have back-ported recently (as columns really are an essential component in all designs these days) and have been meaning to post about, so it was a good kick to get this post going. Thanks @gatogordo!</p>
<p>You may think that the <code>ColumnSplitter</code> component provided in the XM Cloud starter kit looks fairly straightforward and should work in older versions of Sitecore, but there are a few key changes that are required, both in the JS and in Sitecore, which I will go through now.</p>
<p><i><b>Note:</b> I'm going to be using SXA and Tailwind in this post, to get up and running as quickly and painlessly as possible. No doubt this would work without them, but there would be additional / different steps involved.</i></p>
<p><b>TL;DR</b> checkout the repo: <a href="https://github.com/moo2u2/jss-column-splitter-example" rel="nofollow" target="_blank">https://github.com/moo2u2/jss-column-splitter-example</a></p><p><br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb3pKXHJINlpvC1qu7XDJBU0JYO8YhMnVhSfDFeOOvaqQzC6zOaYxm65da9aZub0NmSEv2E4YVgpwvJESLbP0oqtfQhETh5G4LWbFgHDfwt-gsOMv_UC9RDbODVmOtqAmnA_Fp2ck6g4yZwu7Y6WIfFcFpsh6Hs3H8QOABjvL-ZskFchRh5ivm8jhzkw/s1437/column_splitter_2thirds.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="384" data-original-width="1437" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb3pKXHJINlpvC1qu7XDJBU0JYO8YhMnVhSfDFeOOvaqQzC6zOaYxm65da9aZub0NmSEv2E4YVgpwvJESLbP0oqtfQhETh5G4LWbFgHDfwt-gsOMv_UC9RDbODVmOtqAmnA_Fp2ck6g4yZwu7Y6WIfFcFpsh6Hs3H8QOABjvL-ZskFchRh5ivm8jhzkw/s16000/column_splitter_2thirds.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">My repo includes a storybook demo</td></tr></tbody></table><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTvJLyxY3hxDmCykvhLbgKwI8ownrIfoEVLsH1060aQv1PgyYtjsfgGji-FgSuZDj8QJo6XKLjuFqSU0IOis2FFQzo0pTs9xlFQQ0JDkjdGiIHA4NRoQi_uNfHaORXNttxRSyRlc64ZjYLnidTiXq5_SgzQ8HrXyMQhOv2BzIrdQAIrX62B2Fqa5K0ww/s1553/column_splitter_border.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="315" data-original-width="1553" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTvJLyxY3hxDmCykvhLbgKwI8ownrIfoEVLsH1060aQv1PgyYtjsfgGji-FgSuZDj8QJo6XKLjuFqSU0IOis2FFQzo0pTs9xlFQQ0JDkjdGiIHA4NRoQi_uNfHaORXNttxRSyRlc64ZjYLnidTiXq5_SgzQ8HrXyMQhOv2BzIrdQAIrX62B2Fqa5K0ww/s16000/column_splitter_border.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">This is what we're going for!</td></tr></tbody></table><br /><p><br /></p>
<p>First, ensure you have a <a href="https://dev.sitecore.net/Downloads/Sitecore_Experience_Platform/102/Sitecore_Experience_Platform_102.aspx" rel="nofollow" target="_blank">10.2 instance of Sitecore</a> with (the correct version of) <a href="https://dev.sitecore.net/Downloads/Sitecore_Experience_Accelerator/10x/Sitecore_Experience_Accelerator_1020.aspx" rel="nofollow" target="_blank">SXA</a> and <a href="https://dev.sitecore.net/Downloads/Sitecore_Headless_Rendering/20x/Sitecore_Headless_Rendering_2002.aspx" rel="nofollow" target="_blank">Headless</a> modules installed. If you want to restore the items from the repo above, you'll also need <a href="https://doc.sitecore.com/xp/en/developers/102/developer-tools/sitecore-management-services.html" rel="nofollow" target="_blank">Sitecore Management Services</a> (for serialization) installed.</p>
<p>We'll start by creating a headless Tenant and Site in Sitecore, as well as <a href="https://doc.sitecore.com/xp/en/developers/hd/210/sitecore-headless-development/walkthrough--creating-a-jss-next-js-application-with-the-jss-initializer.html">creating a new Next.JS project</a> with the same name as the site, and the correct version for Sitecore 10.2.</p>
<p><code>npx create-sitecore-jss@ver20 nextjs --appName jss-column-splitter-example</code></p>
<p>At this point it's worth running a <code>jss setup</code> to configure your Sitecore connection, and verifying that your app runs correctly in <a href="https://doc.sitecore.com/xp/en/developers/hd/201/sitecore-headless-development/start-a-jss-app-in-connected-mode.html" rel="nofollow" target="_blank">connected mode</a>, because we're about to get into the fun part and add/change a bunch of stuff.</p><p>We'll start by creating a <code>ColumnSplitter</code> rendering, which I originally grabbed from the <a href="https://github.com/sitecorelabs/xmcloud-foundation-head-dev/blob/main/src/sxastarter/src/components/ColumnSplitter.tsx" rel="nofollow" target="_blank">XM Cloud Starter repo</a> (or you can just grab my <a href="https://github.com/moo2u2/jss-column-splitter-example/blob/main/src/components/ColumnSplitter.tsx" rel="nofollow" target="_blank">back-ported version</a>). Next you'll want to add this to your placeholder settings (in my case, jss-main) so that you can add it to the page in Experience Editor.<br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOAcvXRSpmcIm7fFb7h-gDHGnY15NlBHq0ApM3rtlNTmLqfIp6XFPBcEbZfsvZ1La14FKmrEYkH3ImDIhzLtWarFHZtGDmbdztkRZ7nDdIqTjT24FcYH0ogaaMvYzdtofFM81DMPdfE70p_s_kFuaf2v-pdLP0uJbAD7xsvBKVGFmeM-qvVHj12g8xeQ/s923/column_splitter_placeholder.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="277" data-original-width="923" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOAcvXRSpmcIm7fFb7h-gDHGnY15NlBHq0ApM3rtlNTmLqfIp6XFPBcEbZfsvZ1La14FKmrEYkH3ImDIhzLtWarFHZtGDmbdztkRZ7nDdIqTjT24FcYH0ogaaMvYzdtofFM81DMPdfE70p_s_kFuaf2v-pdLP0uJbAD7xsvBKVGFmeM-qvVHj12g8xeQ/s16000/column_splitter_placeholder.png" /></a><br />then go ahead and add the component to your page (in that placeholder), either in Content Editor or Experience Editor. </p><p>At this point it's probably worth taking a pause to explain how the column splitter works: <br />Obviously (by default) it displays no content, so all the settings live in the rendering parameters (no datasource). <br />The rendering parameters are made up of 2 main parts: the grid, and styles. Both contain CSS classes, however they come from different parts of the content tree.</p><p>There are grid + styles options for each column, up to 8 columns (out of the box), as well as the styles for the component itself.</p><p>Rendering parameters live at: <code>/sitecore/templates/Feature/JSS Experience Accelerator/Page Structure/Rendering Parameters/ColumnSplitter</code> or <code>{181740EA-A7AE-4799-A649-A75917570E38}</code> and references a couple of other Templates you may not have: <span style="font-family: courier;">Base Rendering Parameters</span> (<code>{4247AAD4-EBDE-4994-998F-E067A51B1FE4}</code>) and <span style="font-family: courier;">_PerSiteStandardValues</span> (<code>{44A022DB-56D3-419A-B43B-E27E4D8E9C41}</code>).</p>
<p>If you open the rendering parameters at the monent, you'll likely notice that they are not displaying correctly:<br /><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGnwe-BOKNWj6T7HBHye_M30GXIsQsQiBtz4Ae69cQOMLw0LD-ePZJypAo2d9OLGALuCWEOIG8HvchTcpFPH2ojivCi4LnC39WVXAWpc10IXLj2Wtr5euKGF_RfpbS1NiMqan4zAgNR1zFOR5FABSiefk1oUHHQYY1Lwz4XJSR7DeqNPt3ms0QRGKAMA/s721/column_splitter_nostyles.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="539" data-original-width="721" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGnwe-BOKNWj6T7HBHye_M30GXIsQsQiBtz4Ae69cQOMLw0LD-ePZJypAo2d9OLGALuCWEOIG8HvchTcpFPH2ojivCi4LnC39WVXAWpc10IXLj2Wtr5euKGF_RfpbS1NiMqan4zAgNR1zFOR5FABSiefk1oUHHQYY1Lwz4XJSR7DeqNPt3ms0QRGKAMA/s16000/column_splitter_nostyles.png" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaZAuqhigwWL9PS90yJDAgxmVLGscV0yqADo02BqkL22GNTzt6Ek-fzKdydhWOZMCa2rBTGwjNOpALDXQoMGPQZJdUxyGVOaBngtyuY0hu9leyC9yluk9Wvs8_ZtyHrN0EW3cZgbTelAxcNmxVOI44FMMR1DezVaRuvoQffuLvG38Zb_H2mPDgN5GbZA/s716/column_splitter_nogrid.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="538" data-original-width="716" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaZAuqhigwWL9PS90yJDAgxmVLGscV0yqADo02BqkL22GNTzt6Ek-fzKdydhWOZMCa2rBTGwjNOpALDXQoMGPQZJdUxyGVOaBngtyuY0hu9leyC9yluk9Wvs8_ZtyHrN0EW3cZgbTelAxcNmxVOI44FMMR1DezVaRuvoQffuLvG38Zb_H2mPDgN5GbZA/s16000/column_splitter_nogrid.png" /></a></div><br /><p>Starting with styles: as with regular SXA, we need to create a "Styles" item under your Tenant > Site > Presentation, of type: /sitecore/templates/Foundation/Experience Accelerator/Presentation/Styles - {C6DC7393-15BB-4CD7-B798-AB63E77EBAC4}. Under that you can add your styles (I've added various backgrounds for demo purposes).<br /><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtN1V_eHTcQFMLbjrGHlabM6YD-YMtzX1vhFFRVgcM8UJVZ5BeW_QgT4ThluVfCv08jELJxD6Ojd_yyCKYNuP-s_XRyuWkjRvReKzjM0n8XAMrmWBggp-W3HobuSTF9xO_i7LEz19SrVQrlAl9N5Y3T6MyIWoBWJTZ-LOUnoVUTbDo-cvB6DF_Q91Vig/s494/column_splitter_style_items.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="192" data-original-width="494" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtN1V_eHTcQFMLbjrGHlabM6YD-YMtzX1vhFFRVgcM8UJVZ5BeW_QgT4ThluVfCv08jELJxD6Ojd_yyCKYNuP-s_XRyuWkjRvReKzjM0n8XAMrmWBggp-W3HobuSTF9xO_i7LEz19SrVQrlAl9N5Y3T6MyIWoBWJTZ-LOUnoVUTbDo-cvB6DF_Q91Vig/s16000/column_splitter_style_items.png" /></a></div><br />
<p>Now we can see the styles listed in our rendering parameters!<br /><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWUN8KcJBZvszKOoZq9doyHh6Ond-LGxPCWsTSrb3whhi6k2eehdYz2cMZ47hQ0sBQg86Pe2oYBSHOIIHGi0tyYMUbN_hjbQw4MPEoDMMKw_DLtOqSSsT-1vmPUr-JEJx5GaFaf9hqPFPaA486mq16OaxMn_ilmzpZJnjmKbAIYcys1p72mG6x8xgWDA/s685/column_splitter_styles2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="458" data-original-width="685" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWUN8KcJBZvszKOoZq9doyHh6Ond-LGxPCWsTSrb3whhi6k2eehdYz2cMZ47hQ0sBQg86Pe2oYBSHOIIHGi0tyYMUbN_hjbQw4MPEoDMMKw_DLtOqSSsT-1vmPUr-JEJx5GaFaf9hqPFPaA486mq16OaxMn_ilmzpZJnjmKbAIYcys1p72mG6x8xgWDA/s16000/column_splitter_styles2.png" /></a></div><br /><p>The grid parameters are slightly more complex, as they depend on the SXA grid system, so if you aren't familiar with it I would check out the documentation on how to <a href="https://doc.sitecore.com/xp/en/developers/sxa/102/sitecore-experience-accelerator/create-a-custom-grid.html">Create a custom grid</a>.</p><p>Because Bootstrap is old school and we're using headless, let's create a faux "grid" for Tailwind! I've added a few different widths for our columns:<br /><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqE52h8-5BTuZfRdqEBCKi2GAPpZVNE7dsfwimCmCh34ljT-yG4L18gEpOZH5rz7pDOarQsw0DgOGrleat4gTdVus2meuo80Rb1AG-d93RqRnJMkKd0AYeBHEvvKqzio400BzxZjo6jzE07FmELpF1DJJ-99P0iiXMkwWI3b-_4PcSb9r0B4OOlEdczw/s1191/column_splitter_tailwind.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="539" data-original-width="1191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqE52h8-5BTuZfRdqEBCKi2GAPpZVNE7dsfwimCmCh34ljT-yG4L18gEpOZH5rz7pDOarQsw0DgOGrleat4gTdVus2meuo80Rb1AG-d93RqRnJMkKd0AYeBHEvvKqzio400BzxZjo6jzE07FmELpF1DJJ-99P0iiXMkwWI3b-_4PcSb9r0B4OOlEdczw/s16000/column_splitter_tailwind.png" /></a></div><div></div><p>Next we need to update our site settings to have the field for, and reference, this grid: <br />In <code>/sitecore/templates/Foundation/JSS Experience Accelerator/Multisite/JSS Settings</code> - <code>{EC848505-D30C-4BDC-A0AA-7CC9D320085E}</code> add <code>/sitecore/templates/Foundation/Experience Accelerator/Grid/_Grid Mapping</code> - <code>{9D81C61A-0341-4312-816D-E5204385EA3C}</code> to the inherited templates<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbI0tNrsCEwyWB7PXJKEJPv-EtedVqE_7YhItGtp_yIcpLJgCBZNphLggtAek27RoP028BsQb1PKjLYCyhF8m5HPSyvuHnDp6fnz_2sDCO84n8EA8bpO_Svo4Ogr8iG0VupjtH4VPoLpM-FEjzcIXkIXkv0T3KKBSsTFdjgGWR29flbQz4WOfKWx-Qow/s937/column_splitter_grid_settings.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="331" data-original-width="937" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbI0tNrsCEwyWB7PXJKEJPv-EtedVqE_7YhItGtp_yIcpLJgCBZNphLggtAek27RoP028BsQb1PKjLYCyhF8m5HPSyvuHnDp6fnz_2sDCO84n8EA8bpO_Svo4Ogr8iG0VupjtH4VPoLpM-FEjzcIXkIXkv0T3KKBSsTFdjgGWR29flbQz4WOfKWx-Qow/s16000/column_splitter_grid_settings.png" /></a><br /></div><div><br /></div>Back up in our site settings we should see Bootstrap 5 selected as the default grid, which we can now change to our Tailwind grid!<br /><br /><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAyRoeXx2PjuFc8IgwDlFBgE-KCcZg_1TWZjofJYkFR0GdGhtzveA9lVbOrLLBoRmHIC6EodtgTaWGpbwz5e_AS6GzD70kiyg3YtshK_OJhSFFc9c0i-iPZsBAd_M80hr7hCd7b-uP94riRhCHhxrNzwMX3OKcSy2-AWiQSIyMVw9tdFpklln4pjSBgA/s989/column_splitter_grid_setting.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="209" data-original-width="989" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAyRoeXx2PjuFc8IgwDlFBgE-KCcZg_1TWZjofJYkFR0GdGhtzveA9lVbOrLLBoRmHIC6EodtgTaWGpbwz5e_AS6GzD70kiyg3YtshK_OJhSFFc9c0i-iPZsBAd_M80hr7hCd7b-uP94riRhCHhxrNzwMX3OKcSy2-AWiQSIyMVw9tdFpklln4pjSBgA/s16000/column_splitter_grid_setting.png" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">This is the default - change it to our new Tailwind grid</td></tr></tbody></table>
<p>At this point our rendering parameters should all be good and useable!<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH5rU297IJFm8hXngy8qujNtpBy9SthaevwKtaVbH9nWwk0fS2zrSKw1KPGrxQfm3YHPkARfKzUsvrRO_ODP7qkMuwQM20SOcqgFGfL9BXcvoXCRgHS5bTG9pLRV1CuRXvEnwPN0UGDIr0kS4XH3r7T25BrwSXzNCkN_dG1uOFQRXIo6hImfopkmby7A/s683/column_splitter_grid_assigned2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="569" data-original-width="683" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH5rU297IJFm8hXngy8qujNtpBy9SthaevwKtaVbH9nWwk0fS2zrSKw1KPGrxQfm3YHPkARfKzUsvrRO_ODP7qkMuwQM20SOcqgFGfL9BXcvoXCRgHS5bTG9pLRV1CuRXvEnwPN0UGDIr0kS4XH3r7T25BrwSXzNCkN_dG1uOFQRXIo6hImfopkmby7A/s16000/column_splitter_grid_assigned2.png" /></a></div><br /><p>Finally we need to add our placeholder settings for each column. This is one of the key differences in back-porting it, as we don't use any wildcard asterisk. Just a plain old placeholder name.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVAmDuyEkXQH565Xv8i20V6T7wtMjv3NAlDLK0-CQCAy9c2X2BT8DBpKxfFEb2KlpY5Cjo-P0UZ8993DuTB7hVHo6W5_t2kN80JIiGvJ8XWFftLjh3MsvlZ2uBrAsSTmLtqG9rMvYKkS74qhf_B4LnuPfobNZrSpTNcef1VtKRDSgHKZMny76S_9OaGA/s1092/column_splitter_placeholder_columns.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="419" data-original-width="1092" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVAmDuyEkXQH565Xv8i20V6T7wtMjv3NAlDLK0-CQCAy9c2X2BT8DBpKxfFEb2KlpY5Cjo-P0UZ8993DuTB7hVHo6W5_t2kN80JIiGvJ8XWFftLjh3MsvlZ2uBrAsSTmLtqG9rMvYKkS74qhf_B4LnuPfobNZrSpTNcef1VtKRDSgHKZMny76S_9OaGA/s16000/column_splitter_placeholder_columns.png" /></a></div><br /><p>With your ColumnSplitter added to the page, select a size (and optionally a style) for a couple of columns and let's take a look at the layout service to double check it's all good for the app to consume: <br />/sitecore/api/layout/render/default?item=/&sc_apikey={YOUR_KEY}<br /><br />We can see the component added to the placeholder, and values in the grid and styles but those aren't useable values...</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj64q3jf6F6i21pcK3loNhj5Q2EUXf-7Mn_goZlQ0GVZKN5uYs8eUkR0rbTfndlDhFIaU9w22bv8l4FbXQXXCs8pIMGTXDJ1doMj7A_16ag6iCPvs3AiUVOsq3q866rasU2IXN6iBYwKiVeaujXsBuJEvwq7gMG8jpU3sV-rxsJ5cdHyYpC_b3cI2Tdvg/s497/column_splitter_layout_service.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="468" data-original-width="497" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj64q3jf6F6i21pcK3loNhj5Q2EUXf-7Mn_goZlQ0GVZKN5uYs8eUkR0rbTfndlDhFIaU9w22bv8l4FbXQXXCs8pIMGTXDJ1doMj7A_16ag6iCPvs3AiUVOsq3q866rasU2IXN6iBYwKiVeaujXsBuJEvwq7gMG8jpU3sV-rxsJ5cdHyYpC_b3cI2Tdvg/s16000/column_splitter_layout_service.png" /></a></div><p></p><p><br />This is where I introduce you to my mate Andy's blog post on <a href="https://andypaz.com/2021/01/05/serializing-rendering-parameters-in-the-layout-service/">Serializing rendering parameters in the layout service</a>. <br /><b><i>Note</i>:</b> If you have selected multiple grid sizes (for different devices), you will end up with multiple pipe-separated GUIDs, which his blog doesn't cover. You will need to update your code to cater for this.<br />With the fix in place, you should see the rendering parameters serialized in JSON format:<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMGbAb4TvkC_jd9Sszn4E4NBrVPnxZk36HO3xQU5rqf9i4zsisT_HbnPiRCqU_f_67rTmoadysXQ_jd9K4JjGB4kBkvAR7FFwoeji4SzzsaR7VOU7MGboumuejl_GCXOCauxbDkp7LNN8ALtqw4rlV9ZKUdl3WHfZXV1fgJ9zZtDauaVCW0C177Ye2vA/s931/column_splitter_layout_service_fixed.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="473" data-original-width="931" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMGbAb4TvkC_jd9Sszn4E4NBrVPnxZk36HO3xQU5rqf9i4zsisT_HbnPiRCqU_f_67rTmoadysXQ_jd9K4JjGB4kBkvAR7FFwoeji4SzzsaR7VOU7MGboumuejl_GCXOCauxbDkp7LNN8ALtqw4rlV9ZKUdl3WHfZXV1fgJ9zZtDauaVCW0C177Ye2vA/s16000/column_splitter_layout_service_fixed.png" /></a></div><br /><p>We're now in a state where we're good to consume this from the headless app we created at the start! <br />Run your app in connected mode, and input the following into your site settings:</p>
<table style="width:100%"><tbody><tr><th style="width:50%">Server side rendering engine</th><td style="width:50%">http</td></tr><tr><th>Server side rendering engine endpoint URL</th><td>http://localhost:3000/api/editing/render</td></tr><tr><th>ServerSideRenderingEngineApplicationUrl</th><td>http://localhost:3000</td></tr></tbody></table><br />and you should be able to see your component in the Experience Editor!</div><div><br />You can have a play around with different grid sizes and styles, and experiment with adding different content to your placeholders.<br /><p></p><h3 style="text-align: left;">Extra Tidying </h3><p>Since we're using SXA, let's make the "Add randering" popup display the SXA way:<br />Under your Tenant > Site > Presentation, add an "Available Renderings" item of type /sitecore/templates/Foundation/Experience Accelerator/Presentation/Available Renderings/Available Renderings Folder - {39A9ED72-407D-4440-BA50-30956834794E} and add your renderings.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUgDDL2-wz-7xECx63sgz5H_Q8Kv6pSElG1Bo8oMbUk1ufmwCJaMHDM7pJaqeEqrruc92afQSBTtPIcrunlbXLY9zOBdoBnrg5XPcTyjJ6UaT9sY4ILHeR_1SotyJiy0VyByFTXN-o8_s4ds6uIRwlpJ18ymsHfkBkw0vm9f2Gq034h9NTGbTOgsABww/s1055/column_splitter_available_renderings.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="365" data-original-width="1055" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUgDDL2-wz-7xECx63sgz5H_Q8Kv6pSElG1Bo8oMbUk1ufmwCJaMHDM7pJaqeEqrruc92afQSBTtPIcrunlbXLY9zOBdoBnrg5XPcTyjJ6UaT9sY4ILHeR_1SotyJiy0VyByFTXN-o8_s4ds6uIRwlpJ18ymsHfkBkw0vm9f2Gq034h9NTGbTOgsABww/s320/column_splitter_available_renderings.png" width="320" /></a></div><p></p><p>This should make the modal popup much more user-friendly!<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPTgiBHAZbpFc714oAKORJhDgVDuibUsjb4CYOutaMdgPgNRFUWlp45-cYLMMOUFTnG7Bv7iCPMu_YS3sWsP0Ix8BZ_HS_Wwkvr-V31OzkNLpVTlX8J39llvqJADrzu8CdM2freUspqhAIfwyzH6iBhDVD9Ymti0Oo6ihS6UeCkj8OuFu16rk3hxSfiA/s1254/column_splitter_add_rendering2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="954" data-original-width="1254" height="243" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPTgiBHAZbpFc714oAKORJhDgVDuibUsjb4CYOutaMdgPgNRFUWlp45-cYLMMOUFTnG7Bv7iCPMu_YS3sWsP0Ix8BZ_HS_Wwkvr-V31OzkNLpVTlX8J39llvqJADrzu8CdM2freUspqhAIfwyzH6iBhDVD9Ymti0Oo6ihS6UeCkj8OuFu16rk3hxSfiA/s320/column_splitter_add_rendering2.png" width="320" /></a></div><br /></div><br />Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-68922464487835940902023-04-06T09:49:00.004+10:002023-04-06T09:49:44.806+10:00Sitecore Forms long labels truncated<p>The cause of this is fairly straightforward, but given how painful it is to debug I thought I'd post this for anyone else who comes across it.</p>
<p>If you are using Sitecore Forms, and ever have the need to use a very long label in a list item (eg. a checkbox list) you will notice it gets truncated. You may even note that the length is suspiciously similar to the max length of an item name...</p>
<p>After a lot of digging you'll note that these list item label/value strings are altered and set in <code>Sitecore.ExperienceForms.Mvc.DataSource.DataSourceSettingsManager</code>, specifically in the <code>UpdateStaticItems</code> method where there is a call to <code>ItemUtil.ProposeValidItemName()</code> for both label text <u>and</u> value. This is certainly required for one (if it's being set as the name or display name) however not for both.</p>
<p>Thanks to dependency injection the fix is nice and easy to patch in:</p>
<pre class="brush:xml"><?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
<sitecore>
<services>
<register serviceType="Sitecore.ExperienceForms.FieldSettings.IFieldSettingsManager`1[[Sitecore.ExperienceForms.Mvc.Models.ListFieldItemCollection, Sitecore.ExperienceForms.Mvc]], Sitecore.ExperienceForms"
patch:instead="register[@serviceType='Sitecore.ExperienceForms.FieldSettings.IFieldSettingsManager`1[[Sitecore.ExperienceForms.Mvc.Models.ListFieldItemCollection, Sitecore.ExperienceForms.Mvc]], Sitecore.ExperienceForms']"
implementationType="MyProject.Feature.Forms.DataSource.DataSourceSettingsManager, MyProject.Feature.Forms" lifetime="Transient" />
</services>
</sitecore>
</configuration></pre>
<script src="https://gist.github.com/moo2u2/6000ddcc3d85b4499a790228831d7105.js"></script>
Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-21510849256259134942023-04-01T20:05:00.004+11:002023-04-01T20:06:52.208+11:00next-auth error: "Can't resolve module 'next/headers'"<h4 style="text-align: left;">The Issue</h4>
<p>Recently I've been working on a site using Sitecore headless (JSS) with authentication. As we're using Next.JS it makes the most sense to utilise <a href="https://next-auth.js.org/" rel="nofollow" target="_blank">NextAuth.JS</a> which has been around for a while, has super easy setup, and integrates with loads of providers - including a couple which we needed for the project.</p><p>Everything seemed to be working well, until we started using <a href="https://next-auth.js.org/configuration/nextjs#getserversession" rel="nofollow" target="_blank">getServerSession()</a> to fetch the session info server-side, when everything started to fall apart in errors - particularly one mentioning:</p><p></p><blockquote>Can't resolve module 'next/headers' </blockquote>A quick search led to a few results such as <a href="https://github.com/nextauthjs/next-auth/issues/6559" rel="nofollow" target="_blank">issue 6559</a> which indicated we may need to revert to version 4.15.1 (we were using the latest, 4.20.1). This did indeed appear to get around that particular error, though we weren't sure at what cost (and we weren't about to go looking through the change history between those 2 versions). <p></p><p>Instead I made <a href="https://github.com/moo2u2/next-auth-issue-6559" rel="nofollow" target="_blank">a small git repo</a> with a minimal reproduction of the issue (all JSS mocked out), logged <a href="https://github.com/nextauthjs/next-auth/issues/7103" rel="nofollow" target="_blank">issue #7103</a> on the next-auth github repo, and surprisingly heard back from one of the authors within 24 hours with a workaround. Not as good as a fix, of course, but it unblocked us which is definitely a great outcome!</p>
<h4 style="text-align: left;">The Fix</h4><p dir="auto"><i>Checkout my branch <a href="https://github.com/moo2u2/next-auth-issue-6559/tree/patch-fix" rel="nofollow" target="_blank">patch-fix</a> for pre-fixed version.</i></p>
<p>Perform the following steps in your local project:</p>
<p><code>npm install --save-dev patch-package</code></p>
<p>Remove the 'if' block from <a href="https://github.com/nextauthjs/next-auth/blob/40261834111156b0df1318f5a9d8bd652387ff7f/packages/next-auth/src/next/index.ts#L121-L126">packages/next-auth/src/next/index.ts#L121-L139</a> leaving just the 'else' lines <a href="https://github.com/nextauthjs/next-auth/blob/40261834111156b0df1318f5a9d8bd652387ff7f/packages/next-auth/src/next/index.ts#L136-L138">packages/next-auth/src/next/index.ts#L136-L138</a>.</p>
<p><code>npx patch-package next-auth</code></p>
<p>This should resolve the issue for anyone else using the repo!</p>
<p><b>Note: </b>Please keep in mind this is only relevant for Next.JS v12 (not v13) - see <a href="https://github.com/nextauthjs/next-auth/issues/7103#issuecomment-1487888074" rel="nofollow" target="_blank">the comment from balazorban44 in the issue</a>. </p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-48519099574906391782023-03-30T07:45:00.001+11:002023-03-30T19:57:44.059+11:00Sitecore Forms - Markdown in Text Field <p><b><i>Update 30/03/2023</i></b>: Bonus, now with added bold and line breaks!</p>
<hr>
<p>One of the main limitations of Sitecore Forms in my opinion is not having a rich text field. No doubt this is something that could be implemented without too much of a headache, but a simpler option is to allow basic markdown such as links.</p>
<p>eg. this is a sentence with [a link](https://google.com) in it</p>
<p>Fortunately, using a custom field factory (see <a href="https://sitecoreandmore.blogspot.com/2023/03/custom-form-elements-in-headless-jss.html">my previous post</a>) this is pretty simple!</p>
<script src="https://gist.github.com/moo2u2/b52cf1bd225b88d77d50ba814d878811.js"></script>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-23444410946593338462023-03-22T09:20:00.005+11:002023-05-04T11:51:25.079+10:00Custom Form Elements in Headless (JSS) Forms<p>I've been getting pretty deep into the Sitecore React/Next.JS Forms libraries over the last couple of weeks, and while there's some half decent information out there if you look hard enough, it's generally not that accessible or complete. For example, one of the better pages <a href="https://github.com/Sitecore/jss/blob/release/19.0.0/docs/data/routes/docs/techniques/forms/en.md#altering-field-types" rel="nofollow" target="_blank">altering field types</a> is hidden away in GitHub and doesn't seem to pop up for me in a Google search.</p>
<p>I find that Sitecore Forms, while a great form builder, still lacks (at least) a couple of fields which I would consider a must-have: hidden fields, and raw HTML. Fortunately these can be found in <a href="https://github.com/bartverdonck/Sitecore-Forms-Extensions" rel="nofollow" target="_blank">Sitecore Forms Extensions</a>, so we can copy the relevant code from there.</p>
<p>Once you've got the fields into Sitecore - in the form builder and rendering their properties into the layout service - the next step is to ensure your React/Next.JS code knows how to render the fields. Fortunately per the link above, this is fairly easy to inject/extend using a custom <i>field factory</i>: </p><p>
<script src="https://gist.github.com/moo2u2/94c1f7de5d20d49e2ff665b767b90b16.js"></script>
</p><p>We can then add our custom field factory to our form, and we should be good to go!</p>
<pre class="brush:js"><Form
form={fields}
sitecoreApiHost={process.env.SITECORE_API_HOST}
sitecoreApiKey={process.env.SITECORE_API_KEY}
onRedirect={(url: string) => router.push(url)}
<b> fieldFactory={CustomFieldFactory}</b>
/></pre>
<p>Issues / wish list:</p>
<ul>
<li><code>CustomFieldFactory.setComponent</code> first parameter relies on the FieldTypes enum (but works with any string). This causes Typescript errors. It would be great if this parameter/enum were extendable.</li>
<li>The <code>Form</code> component extends <code>Component<<b>FormProps</b>, FormState & FieldStateCollection></code> but is itself not generic! This means that props can't be overridden, which means adding custom properties causes Typescript errors (though the code still works). Ideally Form would be generic so that we could pass custom props and properly override the Form class.</li><li>Conditions are not included anywhere in the Sitecore Forms React code :( See an upcoming post for how we can achieve conditional logic using OOTB Sitecore Forms conditions.</li>
<li>There is no validation on form submission! This was very surprising (and frustrating) to find (and implement).</li>
<li>There is no validation for mandatory fields on a couple of fields, eg. CheckboxList</li>
<li>Fields are still submitted by the JSS code even when adding a 'disabled' attribute to the field. This super painful to work around!</li>
</ul>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-13018088354883912632023-03-13T15:03:00.004+11:002023-03-13T15:03:21.254+11:00Something went wrong. See SPE logs for more details.<p> A nice quick one since I didn't find much useful info around on this particular issue (maybe nobody else has made this stupid mistake).</p><p>While working on a client implementation I came across the following error:</p>
<pre>7952 03:31:46 INFO Script item set to master:\system\Modules\PowerShell\Script Library\JSS SXA\Scaffolding\Content Editor\Insert Item\JSS Site in ScriptSession $scriptSession$|gmnwpaqx1sn0u3gz2hwu5bkr|fef42854-5de9-4f59-ab6a-13edd7d2862e.
ManagedPoolThread #4 03:31:47 ERROR Cannot bind argument to parameter 'TenantTemplatesRoot' because it is null.
7444 03:36:59 WARN Session state elevated for 'ItemSave' by user: sitecore\admin</pre>
<p>Digging into the powershell script which was being called (<code>/sitecore/system/Modules/PowerShell/Script Library/JSS SXA/Scaffolding/Functions/<b>New-JSSSite</b></code>) you can quickly find the relevant line: </p><pre class="brush:ps">$tenantTemplatesRootID = $tenant.Fields['Templates'].Value</pre>
<p>Looking at the fields in my Tenant, I noticed they were all blank! Seems that while transferring items between environments someone forgot to package some of the necessary items.</p>
<p style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid02-qngdt_DRkiCPopSRR0xW70WmjapaWRhLKeohLdTcStW-aBoAcnTAhp96v_eKUbOAa1YX_0RsRE0TEHYokEO9xjVNbxBRScY_8KFczUZvyct84QUtGRkQN94BeNheBK0AqQ1iy36znjL5vT4F_80sxypGcd7lI9IXNGrL7GdbuSWBIizgmHGKAaw/s629/tenant_fields.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="606" data-original-width="629" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid02-qngdt_DRkiCPopSRR0xW70WmjapaWRhLKeohLdTcStW-aBoAcnTAhp96v_eKUbOAa1YX_0RsRE0TEHYokEO9xjVNbxBRScY_8KFczUZvyct84QUtGRkQN94BeNheBK0AqQ1iy36znjL5vT4F_80sxypGcd7lI9IXNGrL7GdbuSWBIizgmHGKAaw/s320/tenant_fields.png" width="320" /></a></p>
<p>Long story short, when packaging up your Tenant don't forget to include:</p>
<ol style="text-align: left;"><li>/sitecore/templates/Project/YourTenant</li><li>/sitecore/media library/Project/YourTenant</li><li>/sitecore/media library/Project/YourTenant/shared</li><li>/sitecore/layout/Renderings/Project/YourTenant</li><li>/sitecore/layout/Placeholder Settings/Project/YourTenant</li></ol>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-22441134813057538882023-03-01T09:34:00.006+11:002023-03-01T09:37:05.274+11:00When Disabling xDB Isn't Enough<p>If you read the documentation on <a href="https://doc.sitecore.com/xp/en/developers/102/platform-administration-and-architecture/using-cms-only-mode-to-run-sitecore-without-the-xdb.html" rel="nofollow" target="_blank">using CMS-only mode to run Sitecore without xDB</a> you'd be forgiven for thinking that a config patch such as this is the simple end of the story:</p>
<pre class="brush: xml"><configuration>
<sitecore>
<settings>
<setting name="Xdb.Enabled" value="false" />
<setting name="Xdb.Tracking.Enabled" value="false" />
</settings>
</sitecore>
</configuration></pre>
<p>Unfortunately as someone on Slack recently discovered, this doesn't help if you're trying to run Sitecore (XP container images, but in CMS-only mode without the xConnect instances) in containers. </p><p>The issue is fairly obvious when you notice CM is not starting and take a look at an example of the logs</p>
<blockquote style="font-family: courier;"><p>920 08:52:34 ERROR Health check Sitecore.XConnect.Client.WebApi.CollectionWebApiClient completed after 0.0038ms with status Unhealthy and 'Error during CollectionWebApiClient initialization: An error occurred while sending the request.'</p><p>1920 08:52:35 ERROR Health check Sitecore.XConnect.Client.WebApi.ConfigurationWebApiClient completed after 0.0054ms with status Unhealthy and 'Error during ConfigurationWebApiClient initialization: An error occurred while sending the request.'</p><p>1920 08:52:36 ERROR Health check Sitecore.XConnect.Client.WebApi.SearchWebApiClient completed after 0.0068ms with status Unhealthy and 'Error during SearchWebApiClient initialization: An error occurred while sending the request.'</p></blockquote><p>Yes, there are in fact health checks on CM and CD which check the status of xConnect, and completely ignore the config settings I mentioned above.</p><p>Not to worry, there are a couple of options at this point:</p><ol style="text-align: left;"><li>Use a config patch to remove the health checks (the easier option)</li><li>Update and override the health checks so that they respect the <code>Xdb.Enabled</code> setting</li></ol>
<h2>Config patch</h2>
<p>Here's an easy patch you can apply which should remove these from your Sitecore configuration and complete the disabling of xDB:</p>
<script src="https://gist.github.com/moo2u2/a614fd38f9dfe1b2e8ae66a9b033962d.js"></script>
<h2>Updating the code</h2>
<p>The "proper" way, I feel, would be to update the code to respect the <code>Xdb.Enabled</code> setting. This is what the setting is indicating, and what the documentation explains that it is for.</p>
<p>To take <code>XConnectCollectionHealthCheckServicesConfigurator</code> as an example:</p>
<pre class="brush: csharp">public class XConnectCollectionHealthCheckServicesConfigurator : Sitecore.XConnect.Client.Configuration.HealthCheckServicesConfigurators.XConnectCollectionHealthCheckServicesConfigurator
{
protected override IHealthCheck CreateCommonWebApiHealthCheck(IServiceProvider provider)
{
<span style="color: #ff00fe;">if (Sitecore.Configuration.Settings.GetBoolSetting("Xdb.Enabled", true))</span>
return base.CreateCommonWebApiHealthCheck(provider);
else
<span style="color: #ff00fe;">return new SuccessHealthCheck();</span>
}
}
public class SuccessHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
return Task.FromResult(HealthCheckResult.Healthy());
}
}</pre>
<p>We add our settings check, and - because there does not seem to be any easy out-of-the-box way to return a successful health check - we also need to create a class for that. Repeat for all 3 health checks, and you're good to go.</p>
<p>Enjoy running your Sitecore topology nice and lean!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-23093703018769452632023-02-19T19:55:00.003+11:002023-02-19T19:55:57.134+11:00Setting up a Sitecore Developer Laptop in 2023<p>So with the start of the new year, and new company, comes the fun part - a purchase of a new laptop. Exciting! I finally get to play with Windows 11 (Pro), and not be restricted by work security.</p>
<p>Long gone are the days of SIF-based installs, with SQL/Solr running as Windows services (and likely many instances of them, if you were working on multiple versions of Sitecore). These days everything can be done on containers and VS Code (yes, even back-end dev). </p>
<p>With the release of (some) <a href="https://raw.githubusercontent.com/Sitecore/docker-images/master/tags/sitecore-tags.md" rel="nofollow" target="_blank">ltsc2022 images</a> over the last week, we can finally <a href="https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-11#windows-client-host-os-compatibility" rel="nofollow" target="_blank">use process isolation on Windows 11</a>! <i>Yes it's in preview, but at least your computer won't have a heart attack from lack of resources!</i></p>
<p>So because it was all so easy, I thought I'd quickly note down the few things I needed to install to get going. As a bonus I've included some configuration that might catch you out, or you may have forgotten if you've done it before.<br /></p>
<h3>Windows features</h3>
<ol><li>Containers / HyperV</li><li>IIS (<i>optional, if you want to do a proper install at some point</i>)<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgjO_MA2LbmHwN8t1WCv0uY3R3uA_H76SEpH-hQe2O8yPEKCX4t84u-UZkGmSpouDg4ffPMTaciuM4cHlIwdr0BEf4h1kCTXgxLaAWg3jMtSeYFhqG_TTnCdElGlwzFqlKw5bszpIyIBpoDN9D3fb_2EcDUIvprqk4M3jHdxHHnVLcpSQoAKEZNqSaoJw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="371" data-original-width="417" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEgjO_MA2LbmHwN8t1WCv0uY3R3uA_H76SEpH-hQe2O8yPEKCX4t84u-UZkGmSpouDg4ffPMTaciuM4cHlIwdr0BEf4h1kCTXgxLaAWg3jMtSeYFhqG_TTnCdElGlwzFqlKw5bszpIyIBpoDN9D3fb_2EcDUIvprqk4M3jHdxHHnVLcpSQoAKEZNqSaoJw" width="270" /></a></div><br /><br /></li></ol>
<h3>Software</h3>
<ol><li><a href="https://code.visualstudio.com/download" rel="nofollow" target="_blank">VS Code</a> for most dev, <i>plus extensions:</i>
<ol><li>Azure (Account / Functions / App Service / Cache / Kubernetes Service / Pipelines / Resources / Static Web Apps / Storage)<br /></li>
<li>Docker</li><li>Git History</li><li>GraphQL (Language Feature Support / Syntax Highlighting)<br /></li>
<li>IntelliCode</li>
<li>PowerShell</li>
<li>Prettier - Code Formatter <br /></li>
<li>Scriban</li><li>XML Tools</li>
<li>YAML<br /><br /></li></ol>
</li><li><a href="https://visualstudio.microsoft.com/" rel="nofollow" target="_blank">Visual Studio</a> (<i>probably optional, since VS Code can do everything these days!)</i><br /></li><ol><li><a href="https://www.teamdevelopmentforsitecore.com/Download/TDS-Classic" rel="nofollow" target="_blank">TDS</a> (<i>optional, only if your clients use it</i>)<br /><br /></li></ol>
<li><a href="https://nodejs.org/en/download/" rel="nofollow" target="_blank">Node</a></li>
<ol><li><a href="https://doc.sitecore.com/xp/en/developers/hd/210/sitecore-headless-development/install-the-jss-cli-globally.html" rel="nofollow" target="_blank">Sitecore JSS CLI<br /><br /></a></li></ol>
<li><a href="https://git-scm.com/downloads" rel="nofollow" target="_blank">Docker Desktop</a></li>
<li><a href="https://git-scm.com/downloads" rel="nofollow" target="_blank">Git <br /></a><br /></li>
<li>Make life easier: <a href="https://www.7-zip.org/download.html" rel="nofollow" target="_blank">7Zip </a>/ <a href="https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms">SQL Server Management Studio</a></li>
<li>Testing APIs: <a href="https://www.postman.com/downloads/" rel="nofollow" target="_blank">Postman</a></li>
<li>Decompiling: <a href="https://apps.microsoft.com/store/detail/ilspy/9MXFBKFVSQ13" rel="nofollow" target="_blank">ILSpy</a> / <a href="https://www.jetbrains.com/decompiler/download/" rel="nofollow" target="_blank">DotPeek</a> <br /></li>
<li>Storage access: <a href="https://filezilla-project.org/download.php" rel="nofollow" target="_blank">Filezilla</a> & <a href="https://azure.microsoft.com/en-us/products/storage/storage-explorer" rel="nofollow" target="_blank">Azure Storage Explorer</a><br /></li>
<li>Remote access: <a href="https://apps.microsoft.com/store/detail/microsoft-remote-desktop/9WZDNCRFJ3PS" rel="nofollow" target="_blank">Microsoft Remote Desktop</a> and Client VPNs</li></ol>
<h3>Configuration</h3>
<p>Visual Studio:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEir6hrh1q-s2vuuuUun12O4vIV4EhjA8TphoJRSZy04cgvtvBJJDHE7V5f5X4NVB2lDdUR1NMDdI2-ThBLIH03ACwiT4yjjY1zeENTBRlT2nunOEcv3YHuZ-W9OyH-f7j4bV2o15I3Npi4BEd5-wC6-UXgHWKAsuf_1VS9RVHDYF8o1w5yUYz9PtV-2rg" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="564" data-original-width="850" height="212" src="https://blogger.googleusercontent.com/img/a/AVvXsEir6hrh1q-s2vuuuUun12O4vIV4EhjA8TphoJRSZy04cgvtvBJJDHE7V5f5X4NVB2lDdUR1NMDdI2-ThBLIH03ACwiT4yjjY1zeENTBRlT2nunOEcv3YHuZ-W9OyH-f7j4bV2o15I3Npi4BEd5-wC6-UXgHWKAsuf_1VS9RVHDYF8o1w5yUYz9PtV-2rg" width="320" /></a></div><br /> <p></p>
<p>Docker settings:</p><p>Turn off <span style="font-family: courier;">docker-compose v2</span> (for now, until support is added by the Sitecore team) <br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhpDeevB79Mmw5Bg3UB8ofoW9x2IToAlIae6bOEiXPdg2boHqOLCLAuD4kYQ6E8FW4J2phSR-zd78Gz6XvJJLOeySOTD9T44N9rwn-V4x3joRWDkqqYX_uROhuE2hP_tCJN6n8e0a4mUMbOPOJ21ps5QAv6X1Sl97R4Qd4HPK2OZB4_PVHBdJ7K-Ic_-g" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="45" data-original-width="491" height="29" src="https://blogger.googleusercontent.com/img/a/AVvXsEhpDeevB79Mmw5Bg3UB8ofoW9x2IToAlIae6bOEiXPdg2boHqOLCLAuD4kYQ6E8FW4J2phSR-zd78Gz6XvJJLOeySOTD9T44N9rwn-V4x3joRWDkqqYX_uROhuE2hP_tCJN6n8e0a4mUMbOPOJ21ps5QAv6X1Sl97R4Qd4HPK2OZB4_PVHBdJ7K-Ic_-g" width="320" /></a></div><br />Set the following Docker settings:<p></p>
<pre>"features": {
"buildkit": false
}</pre>
<hr />
<p>So only a dozen or so tools, plus a little setup and configuration. Pretty great if you ask me! <br />If there's anything I'm missing that you use to be productive I'd love to hear it. Leave a comment and I'll take a look (and hopefully make my life easier in the process).<br /></p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-57589564566664980072023-02-09T21:07:00.002+11:002023-02-09T21:07:43.215+11:00Moving On (but more Sitecore)For those who know me you'll know that I've worked with Sitecore in the APJ Professional Services team for the last 4 1/2 years, and I've finally made the decision to move on to something new. It's been a heck of a journey, from being a part of the team while there were only a handful of us, to leading the team across APJ; working with a massive range of clients and partners, from those who had zero Sitecore experience, to those who had Sitecore MVPs on board.
It's funny how you think you know a lot about Sitecore until you actually go to work there and have access to all the knowledge of the global teams, whether it's Professional Services, Support, or the Product team. Like any software vendor (or product) they have their flaws (after all, the company is made up of humans), but I could not have asked for a better group of people to work with, learn, and grow. I like to think that our Services team in APJ is one of the strongest group of people in the Sitecore ecosystem.
So why move on? There were a number of reasons, but like any other job after a few years things start to get a little less interesting and less challenging, so I made the call to try something (semi-)new. I'll still be in the Sitecore ecosystem, and I'll be hopefully posting some interesting material as I go.
Now that I'm not an employee of Sitecore (and nothing to gain) I'll leave you with a parting word of advice: whether you're a Sitecore newbie or an MVP, client or partner side, there is always a benefit to involving the Sitecore Professional Services team in your life. They have access to more knowledge and internal resources than you could ever get access to, and with all the new Sitecore products being released it's pretty much impossible for one person to be an expert at all of them. So if you want your projects to go smoothly I'd strongly encourage you to get them involved, whether it's a few hours here and there, or a dedicated resource to help you on a recurring basis, you'll be massively reducing your risk and helping to ensure your projects are successful. Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-58509867379034524462022-04-20T13:36:00.007+10:002022-04-20T13:38:41.563+10:00Sitecore Next.JS SSG + SSR (same site)<p>Recently I was working with a client who was implementing a Next.JS app, and wanted the benefits (ie. speed) of SSG but with a few exceptional pages using SSR (for Forms etc.). Note in <a href="https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/implement-a-sitecore-form-in-a-jss-next-js-app.html" rel="nofollow" target="_blank">the Sitecore Next.JS Forms documentation</a>, there is an unfortunate requirement (currently at least) of: "you are server-side rendering the route displaying the form".</p><p>So SSG + SSR in one Next.JS app, no problem! </p><p>There are a couple of considerations, however, so let's look at the steps involved:</p><ol style="text-align: left;"><li><a href="https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/walkthrough--creating-a-jss-next-js-application-with-the-jss-cli.html#create-a-jss-next-js-application" rel="nofollow" target="_blank">Create your Next.JS app</a> with <code>--fetchWith REST --prerender SSG</code> (we'll add an exception for the SSR page(s))<br /></li><li>Now that you've got an app <a href="https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/implement-a-sitecore-form-in-a-jss-next-js-app.html" rel="nofollow" target="_blank">create your Next.JS Form</a></li><li>Add a page with your form on it, both in Sitecore and code (eg. src\pages\contactus.tsx). Make sure you're using <code>getServerSideProps()</code> for SSR.<br /></li><ul><li>If we navigate to the page at this point you should actually see the (incorrect) content (from the <b>homepage</b>). </li><li><a href="https://nextjs.org/docs/advanced-features/debugging#server-side-code" rel="nofollow" target="_blank">Debug the server-side code</a> and take a look at the <code>sitecoreContext</code> and you'll see it's actually resolving as the root path '/'<br /></li></ul><li>Looking at the <a href="https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/routing-and-page-composition-in-jss-next-js-apps.html" rel="nofollow" target="_blank">Sitecore Documentation for Routing</a> we can see <code>lib\page-props-factory.ts</code> is responsible for setting the <code>sitecoreContext</code> and fetching the layout data. Look around line 91 (headless v19) and you'll see a call to <code>extractPath(context.params)</code><br /></li><ul><li>Let's compare our context for the styleguide:</li><ul><li>{<br /> "defaultLocale": "en",<br /> "locales": [ "en", "da-DK" ],<br /> "locale": "en",<br /> <span style="color: #e69138;"> "params": {<br /> "path": [ "styleguide" ]<br /> }</span><br />} <br /></li></ul><li>vs our context for our SSR page:</li><ul><li>{<br /> "defaultLocale": "en",<br /> "locale": "en",<br /> "locales": ["en", "da-DK"],<br /> "query": {},<br /> "req": {...},<br /> "res": {...},<br /> <span style="color: #ffa400;">"resolvedUrl": "/contactus"</span><br />}<br /></li></ul><li>So we'll need to update our line which sets the path:<br />const path = <span style="color: #b6d7a8;">context.resolvedUrl ?? </span>extractPath(context.params);</li></ul><li>Finally, let's not forget to <a href="https://doc.sitecore.com/xp/en/developers/hd/190/sitecore-headless-development/walkthrough--customizing-build-time-static-paths-in-jss-next-js-apps.html" rel="nofollow" target="_blank">update our getStaticPaths / sitemap to exclude our SSR pages</a><br /></li></ol><p>Enjoy your speedy headless Sitecore site using both SSR + SSG!<br /></p><p><br /></p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-64504327115421018332021-02-24T14:42:00.004+11:002021-02-24T14:48:03.585+11:00Sitecore CM+Identity Behind Proxy or CDN<p>I recently worked with a client who had set their Sitecore login servers (SI, CM) behind Cloudflare and an Azure App Gateway, and was using URL re-writing to transform the public URL to the internal URL.<br />For example:<br /></p><ul><li>User -> https://si.cdn.domain.com.au -> Cloudflare -> AWW -> https://si.domain.com.au -> <i>login</i> </li><li>User -> https://cm.cdn.domain.com.au -> Cloudflare -> AGW -> https://cm.domain.com.au -> <i>authoring</i></li></ul><p>They were (understandably) encountering some issues with the setup, as this isn't entirely "out-of-the-box", so I was happy to look into it.</p><p></p><p>The first thing to check was the usual 2 settings to get CM and SI to communicate:<br /></p><p>On CM:</p><pre class="brush: xml"><sc.variable name="identityServerAuthority" value="https://si.cdn.domain.com.au" /></pre><p>And on SI:</p><pre class="brush: xml"><AllowedCorsOriginsGroup1>https://cm.cdn.domain.com.au|https://cm.domain.com.au</AllowedCorsOriginsGroup1></pre><p>(as well as the shared secret).</p><p>Once ensuring those were set correctly, it was time to compare the request / response flow during login. You can do this by opening the network tab in your browser (make sure to "preserve log" in Chrome or "persist log" in Firefox as it's navigating multiple URLs) </p><p>I've copied the flow from my local installation here for reference (hostnames in bold, as you'll notice them also in the <i>ReturnUrl </i>and <i>redirect_uri </i>parameters):</p>
<ol><li> GET 302 https://<b>sc93.sc</b>/sitecore/ <br />
o Location: /identity/login/shell/SitecoreIdentityServer</li>
<li>GET 200 https://<b>sc93.sc</b>/identity/login/shell/SitecoreIdentityServer</li>
<li>POST 302 https://<b>sc93.sc</b>/identity/externallogin?authenticationType=SitecoreIdentityServer&ReturnUrl=/identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default&sc_site=shell
<ul>
<li>Location: https://<b>sc93.identityserver</b>/connect/authorize?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2F<b>sc93.sc</b>%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0</li></ul></li>
<li>GET 302 https://<b>sc93.identityserver</b>/connect/authorize?client_id=Sitecore&response_mode=form_post&response_type=code id_token token&scope=openid sitecore.profile&state=OpenIdConnect.AuthenticationProperties=7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https://<b>sc93.sc</b>/identity/signin&sc_account_prefix=sitecore\&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0
<ul>
<li>Location: https://<b>sc93.identityserver</b>/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DSitecore%26response_mode%3Dform_post%26response_type%3Dcode%2520id_token%2520token%26scope%3Dopenid%2520sitecore.profile%26state%3DOpenIdConnect.AuthenticationProperties%253D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ%26nonce%3D637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk%26redirect_uri%3Dhttps%253A%252F%252Fsc93.sc%252Fidentity%252Fsignin%26sc_account_prefix%3Dsitecore%255C%26x-client-SKU%3DID_NET451%26x-client-ver%3D5.2.2.0</li></ul></li>
<li>GET 200 https://<b>sc93.identityserver</b>/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2F<b>sc93.sc</b>%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0</li>
<li>POST 302 https://<b>sc93.identityserver</b>/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2F<b>sc93.sc</b>%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0<ul>
<li><i>(with form data)</i></li>
<li> Location: /connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code%20id_token%20token&scope=openid%20sitecore.profile&state=OpenIdConnect.AuthenticationProperties%3D7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https%3A%2F%2F<b>sc93.sc</b>%2Fidentity%2Fsignin&sc_account_prefix=sitecore%5C&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0</li></ul></li>
<li>GET 200 https://sc93.identityserver/connect/authorize/callback?client_id=Sitecore&response_mode=form_post&response_type=code id_token token&scope=openid sitecore.profile&state=OpenIdConnect.AuthenticationProperties=7UlGkB3gXympJUDslCAOSCQC-0eKHOhNaTCtSlzF843kL5V2tIaC_aVmfo34CAm-4vvHkTIu3uDTqn0_v2E3w6-gIIBeEnkba9krTHrfVv-NWPu_8v4jLSUxfAAhtEFEoQQgqmpFKWA1_FUJcmkfJYNi7iMZh0sq2-rAhwDsHCL4vwfo1KCaHllSXwpOn4X3E4piKcvdzsKooqBu3iqaKQ&nonce=637489570997897318.MjNhNGUwMGItZjg2My00YWJjLTk1NjgtNzMwMmNkZDQ4OTIyZTkyOWU3NmEtNjZkYy00MTYxLTg4M2UtY2Q4YWVlZDljNzNk&redirect_uri=https://<b>sc93.sc</b>/identity/signin&sc_account_prefix=sitecore\&x-client-SKU=ID_NET451&x-client-ver=5.2.2.0</li>
<li>POST 302 https://<b>sc93.sc</b>/identity/signin<br />
o Location: /identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default</li>
<li>GET 302 https://<b>sc93.sc</b>/identity/externallogincallback?ReturnUrl=&sc_site=shell&authenticationSource=Default
<ul><li>Location: https://<b>sc93.sc</b>/sitecore/client/Applications/Launchpad?sc_lang=en</li></ul></li>
<li>GET https://<b>sc93.sc</b>/sitecore/client/Applications/Launchpad?sc_lang=en
</li></ol>
<p>The first issue was that the user was ending up at the non-proxy/cdn URL for CM, whereas they should be requesting the proxy URL the whole time.</p>
<p>Going through the OWIN pipelines I noticed this appeared to be occuring in <i>Sitecore.Owin.Authentication.IdentityServer.Pipelines.IdentityProviders.ConfigureIdentityServer</i>, and what do you know? Inside this class we see it calls a BuildRedirectUri() method which actually accepts a "CallbackAuthority". Never heard of it? Neither had I! But hey it's right there commented out at the top of <i>App_Config\Sitecore\Owin.Authentication.IdentityServer\Sitecore.Owin.Authentication.IdentityServer.config</i>!</p>
<pre class="brush: xml"><!-- Fill the FederatedAuthentication.IdentityServer.CallbackAuthority setting if you need another host to receive callbacks from IdentityServer. It is useful for reverse proxy configuration. -->
<!--<setting name="FederatedAuthentication.IdentityServer.CallbackAuthority" value="http://proxy" />--></pre>
<p>And after a quick search around the net it does seem like a couple of others have encountered it too. My bad!</p><p>So let's set it: <code><setting name="FederatedAuthentication.IdentityServer.CallbackAuthority" value="https://si.cdn.domain.com.au" /></code></p>
<p>
</p><p>Great, we're making it a bit further, but still hitting the non-proxy URL... Digging further into the pipeline we get to <i>Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.GetStartUrl </i>where we can see it's calling the <i>GetStartUrlPipeline</i>, however it's not using our proxy URL, it's using the request URL - which, by the time it hits this server, has already been transformed! Let's fix that with a quick patch:</p><p><script src="https://gist.github.com/moo2u2/d50a9cfb9a957fec4e7d2f8631658a12.js"></script></p>
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.signedIn>
<processor type="Identity.Pipelines.CookieAuthentication.CustomGetStartUrl,Identity"
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.GetStartUrl, Sitecore.Owin.Authentication']" resolve="true" />
</owin.cookieAuthentication.signedIn>
</pipelines>
</sitecore>
</configuration></pre>
<p>Perfect, now we're staying on the proxy hostname, but getting redirected to a "not found" page... Ok let's see if there are any other spots in the code where it's using the request URL rather than the CallbackAuthority (proxy) URL. Yep we have one in GoToLoginPage. Let's patch that one too:</p>
<script src="https://gist.github.com/moo2u2/16d44d37648c5cb45ba98ed16cdd3348.js"></script>
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.applyRedirect>
<processor type="Identity.Pipelines.ApplyRedirect.CustomGoToLoginPage, Identity" resolve="true"
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ApplyRedirect.GoToLoginPage, Sitecore.Owin.Authentication']" />
</owin.cookieAuthentication.applyRedirect>
</pipelines>
</sitecore>
</configuration></pre>
<p>Now we're seeing an error in the logs:</p>
<pre>8604 04:31:54 ERROR Only local URLs are allowed.
Exception: Sitecore.Exceptions.SecurityException
Message: Only local URLs are allowed.
Source: Sitecore.Kernel
at Sitecore.Web.Authentication.DefaultTicketManager.CheckOnExternalUrl(String startUrl)
at Sitecore.Web.Authentication.DefaultTicketManager.CreateTicket(String userName, String startUrl, Boolean persist)
at Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CreateTicket.Process(SignedInArgs args)</pre>
<p>Sitecore is trying to create a "ticket" to manage the logged in users, however it's using the proxy URL which it doesn't know about. For this one, we'll have to swap the URL back to the non-proxy URL with another quick patch:</p>
<script src="https://gist.github.com/moo2u2/309e71d8b8627b6aea3681c894d792c7.js"></script>
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.cookieAuthentication.signedIn>
<processor type="Identity.Pipelines.CookieAuthentication.CustomCreateTicket,Identity"
patch:instead="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignedIn.CreateTicket, Sitecore.Owin.Authentication']" resolve="true" />
</owin.cookieAuthentication.signedIn>
</pipelines>
</sitecore>
</configuration></pre>
<p>And with that, we're finally able to log in and see the dashboard!</p>Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-30086404749211187642020-02-21T14:51:00.000+11:002020-02-21T15:27:58.693+11:00xDB Migration Wizard - Complex Facets and Facet ListsThe <a href="https://doc.sitecore.com/developers/dmt/30/xdb-data-migration-tool/en/map-data-from-mongodb-to-contact-facet.html">documentation for mapping custom facets</a> using the xDB migration wizard is quite good for simple facets (ie. 1-1 string field mapping between Mongo and xConnect), however I've come across a few clients now who have needed something a bit more complex - generally in the form of a facet list.<br />
<br />
I'm going to assume that you've been through the documentation in the link above and successfully mapped one or more simple facets.<br />
<br />
I'm going to run through the scenario of migrating a Contact facet called <i>CustomFacet</i> which has a <code>IElementCollection<CustomObject></code> called <i>CustomObjects</i> in 8.x Mongo to a Contact facet called <i>CustomFacet</i> which has a <code>Dictionary<string, CustomObject></code> called <i>CustomObjects</i> in 9.x xConnect. CustomObject has 2 fields:<br />
<ul>
<li>Id: int </li>
<li>Start: DateTime</li>
<li>Name: string</li>
</ul>
<br/>
<b>NOTE</b>: Before you do anything, make sure you've <a href="https://doc.sitecore.com/developers/93/sitecore-experience-platform/en/deploy-a-custom-model.html">built and deployed your custom model(s)</a>. Verify that the json looks correct, and all your properties are appearing in your json file!<br />
Our json file looks like:<br />
<pre class="brush: js">{
"Name": "My.CustomModel",
"Version": "1.0",
"References": [
{
"Name": "XConnect",
"Version": "1.0"
},
{
"Name": "Sitecore.XConnect.Collection.Model",
"Version": "9.0"
}
],
"Types": {
"My.CustomFacet": {
"Type": "Facet",
"BaseType": "Sitecore.XConnect.Facet",
"ClrType": "My.CustomFacet, ModelSerializer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Properties": {
"CustomObjects": {
"Type": {
"String": "My.CustomObject"
}
}
}
},
"My.CustomObject": {
"Type": "Complex",
"ClrType": "My.CustomObject, ModelSerializer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Properties": {
"Id": {
"Type": "Int32"
},
"Start": {
"Type": "DateTime"
},
"Name": {
"Type": "String"
}
}
}
},
"Facets": [
{
"Target": "Contact",
"Name": "Custom",
"Type": "My.CustomFacet"
}
]
}
</pre>
<br />
our json in Mongo looks like:<br />
<pre class="brush: js">{
"_id" : LUUID("a1b928fb-1ba9-824c-8fa7-2b5bc92b36c9"),
"Identifiers" : {
"Identifier" : "fb28b9a1-a91b-4c82-8fa7-2b5bc92b36c9"
},
"CustomFacet" : {
"CustomObjects" : {
"1" : {
"Id" : 58,
"Name" : "My First Object",
"Date" : ISODate("2020-02-20T07:28:36.376Z")
},
"2" : {
"Id" : 57,
"Name" : "My Second Object",
"Date" : ISODate("2020-02-18T07:28:36.376Z")
},
"3" : {
"Id" : 102,
"Name" : "My Third Object",
"Date" : ISODate("2020-02-17T07:28:36.376Z")
}
}
}
}
</pre>
<br />
I'll be using the Addresses facet as a reference, as this is very similar. We'll just need to edit one file:<br />
<br />
<pre class="brush: csharp">
[SupportedIds(new string[] {"{85C3A33B-AAD7-4982-98E5-D67729DDBCA8}"})]
public class CustomEntriesFromXdbListValueAccessorConverter : BaseItemModelConverter<IValueAccessor>
{
public CustomEntriesFromXdbListValueAccessorConverter(IItemModelRepository repository)
: base(repository)
{
}
protected override ConvertResult<IValueAccessor> ConvertSupportedItem(
ItemModel source)
{
DictionaryEntriesFromDocumentValueReaderConverter valueReaderConverter1 = new DictionaryEntriesFromDocumentValueReaderConverter(this.ItemModelRepository);
valueReaderConverter1.IgnoreValidation = true;
valueReaderConverter1.DictionaryFieldName = "CustomObjects";
DictionaryEntriesFromDocumentValueReaderConverter valueReaderConverter2 = valueReaderConverter1;
valueReaderConverter2.FieldsWithValuesForTheKeysToExclude = new string[0];
ValueAccessor valueAccessor = new ValueAccessor();
ConvertResult<IValueReader> convertResult = valueReaderConverter2.Convert(source);
if (convertResult.WasConverted)
valueAccessor.ValueReader = convertResult.ConvertedValue;
return this.PositiveResult((IValueAccessor) valueAccessor);
}
}
</pre>
<br />
<h2>
Specify data to read from MongoDB</h2>
Navigate to <code>/sitecore/system/Data Exchange/xDB Data Migration 8x to 9/Data Access/Value Accessor Sets/Providers/MongoDB</code> and create the following items and fields:
<br />
<ul class="sitecore-content-hierarchy">
<li>MongoDB
<ul>
<li>MongoDB Contact
<ul>
<li>CustomFacet on MongoDB Contact
<table><tbody>
<tr>
<td>Field Name:</td><td>CustomFacet</td></tr>
<tr><td>Converter Type:</td><td>Sitecore.DataExchange.Providers.MongoDB.Converters.DataAccess.ValueAccessors.DocumentFieldValueAccessorConverter, Sitecore.DataExchange.Providers.MongoDB</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li>MongoDB Contact CustomObject List
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/MongoDB/Data Access/Value Accessor Sets/MongoDB Document Value Accessor Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.ValueAccessors.ChildBasedValueAccessorSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>CustomObject Entries on MongoDB Contact
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Tools/xDB Data Migration Tool/Data Access/Value Accessors/MongoDB Non Preferred Entries from xDB List Value Accessor</td></tr>
<tr><td>Converter Type:</td><td>My.CustomEntriesFromXdbListValueAccessorConverter, My</td></tr>
</tbody></table>
</li>
</ul>
</li>
<li>MongoDB CustomObject Entry
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/MongoDB/Data Access/Value Accessor Sets/MongoDB Document Value Accessor Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.ValueAccessors.ChildBasedValueAccessorSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>Id
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Tools/xDB Data Migration Tool/Data Access/Value Accessors/MongoDB Preferred Entry from xDB List Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.MongoDB.Converters.DataAccess.ValueAccessors.DocumentFieldValueAccessorConverter, Sitecore.DataExchange.Providers.MongoDB</td></tr>
</tbody></table>
</li>
<li>Date
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Tools/xDB Data Migration Tool/Data Access/Value Accessors/MongoDB Preferred Entry from xDB List Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.MongoDB.Converters.DataAccess.ValueAccessors.DocumentFieldValueAccessorConverter, Sitecore.DataExchange.Providers.MongoDB</td></tr>
</tbody></table>
</li>
<li>Name
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Tools/xDB Data Migration Tool/Data Access/Value Accessors/MongoDB Preferred Entry from xDB List Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.MongoDB.Converters.DataAccess.ValueAccessors.DocumentFieldValueAccessorConverter, Sitecore.DataExchange.Providers.MongoDB</td></tr>
</tbody></table>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>
Specify data to write to xConnect contact facet</h2>
Navigate to <code>/sitecore/system/Data Exchange/xDB Data Migration 8x to 9/Data Access/Value Accessor Sets/Providers/xConnect</code>
<br />
<ul class="sitecore-content-hierarchy">
<li>xConnect
<ul>
<li>xConnect Contact
<ul>
<li>CustomFacet on xConnect Contact
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessors/xConnect Entity Facet Value Accessor</td></tr>
<tr><td>Facet Definition:</td><td>Collection Models/xDB Data Migration Tool/xDB Migratino Collection Model/Facets/Contact/CustomFacet</td></tr>
<tr><td>Mapping Set:</td><td>Value Mapping Sets/MongoDB to xConnect Contact Mappings/MongoDB CustomObjects to xConnect Contact CustomFacet</td></tr>
<tr><td>Converter Type:</td><td>Sitecore.DataExchange.Providers.XConnect.Converters.DataAccess.EntityFacetValueAccessorConverter, Sitecore.DataExchange.Providers.XConnect</td></tr>
</tbody></table>
</li>
</ul>
</li>
<li>xConnect Contact CustomFacet
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessor Sets/xConnect Entity Facet Value Accessor Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.ValueAccessors.ChildBasedValueAccessorSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>CustomObjects on CustomFacet on xConnect Contact
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessors/xConnect Entity Facet Dictionary Property Value Accessor</td></tr>
<tr><td>Facet Property:</td><td>Collection Models/xDB Data Migration Tool/xDB Data Migration Collection Model/Facets/Contact/CustomFacet/CustomObjects</td></tr>
<tr><td>Mapping set:</td><td>Value Mapping Sets/MongoDB to xConnect Contact Mappings/MongoDB CustomObjects to xConnect Contact CustomObjects</td></tr>
<tr><td>Converter Type:</td><td>Sitecore.DataExchange.Providers.XConnect.Converters.DataAccess.EntityFacetDictionaryPropertyValueAccessorConverter, Sitecore.DataExchange.Providers.XConnect</td></tr>
</tbody></table>
</li>
</ul>
</li>
<li>xConnect Contact CustomObject
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessor Sets/xConnect XObject Value Accessor Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.ValueAccessors.ChildBasedValueAccessorSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>Id
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessors/xConnect XObject Property Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.XConnect.Converters.DataAccess.XObjectPropertyValueAccessorConverter, Sitecore.DataExchange.Providers.XConnect</td></tr>
</tbody></table>
</li>
<li>Date
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessors/xConnect XObject Property Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.XConnect.Converters.DataAccess.XObjectPropertyValueAccessorConverter, Sitecore.DataExchange.Providers.XConnect</td></tr>
</tbody></table>
</li>
<li>Name
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Providers/xConnect/Data Access/Value Accessors/xConnect XObject Property Value Accessor</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Providers.XConnect.Converters.DataAccess.XObjectPropertyValueAccessorConverter, Sitecore.DataExchange.Providers.XConnect</td></tr>
</tbody></table>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>
Configure mapping from MongoDB contact to xConnect contact facet</h2>
Navigate to <code>/sitecore/system/Data Exchange/xDB Data Migration 8x to 9/Value Mapping Sets/MongoDB to xConnect Contact Mappings</code>
<br />
<ul class="sitecore-content-hierarchy">
<li>MongoDB to xConnect Contact Mappings
<ul>
<li>MongoDB Contact to Contact Model
<ul>
<li>CustomFacet
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping</td></tr>
<tr><td>Source Accessor:</td><td>CustomFacet on MongoDB Contact</td></tr>
<tr><td>Target Accessor:</td><td>Data Access/Value Accessor Sets/Providers/xConnect/xConnect Contact/CustomFacet on xConnect Contact</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.MappingConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
</li>
</ul>
</li>
<li>MongoDB CustomObjects to xConnect Contact CustomFacet
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.ChildBasedMappingSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>CustomObjects
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping</td></tr>
</tbody></table>
</li>
Source accessor:<br />
CustomObject Entries on MongoDB Contact<br />
Target aceessor:<br />
Data Access/Value Accessor Sets/Providers/xConnect/xConnect Contact CustomFacet/CustomObjects on CustomFacet on xConnect Contact<br />
</ul>
</li>
</ul>
</li>
</ul>
<br />
<li>MongoDB CustomObject to xConnect Contact CustomObject
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping Set</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.ChildBasedMappingSetConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
<ul>
<li>Id
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping</td></tr>
<tr><td>Source Accessor:</td><td>Id on MongoDB Contact CustomObject</td></tr>
<tr><td>Target Accessor:</td><td>Data Access/Value Accessor Sets/Providers/xConnect/xConnect Contact CustomObject/Id</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.MappingConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
</li>
<li>Name
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping</td></tr>
<tr><td>Source Accessor:</td><td>Id on MongoDB Contact CustomObject</td></tr>
<tr><td>Target Accessor:</td><td>Data Access/Value Accessor Sets/Providers/xConnect/xConnect Contact CustomObject/Id</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.MappingConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
</li>
<li>Date
<table><tbody>
<tr><td>Template:</td><td>/sitecore/templates/Data Exchange/Framework/Data Access/Mapping/Value Mapping</td></tr>
<tr><td>Source Accessor:</td><td>Id on MongoDB Contact CustomObject</td></tr>
<tr><td>Target Accessor:</td><td>Data Access/Value Accessor Sets/Providers/xConnect/xConnect Contact CustomObject/Id</td></tr>
<tr><td>Converter type:</td><td>Sitecore.DataExchange.Converters.DataAccess.Mappings.MappingConverter, Sitecore.DataExchange</td></tr>
</tbody></table>
</li>
</ul>
</li>
Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-15060227106397937302019-12-10T20:59:00.003+11:002019-12-10T21:01:03.894+11:00Adding a new SXA page sectionFor those of you who aren't aware, SXA comes with 3 main page sections: header, main, and footer. You can drag and drop components into these sections, add a partial design to a section (which then claims the entire section so you can no longer add other components to it), and select whether you would like a section to have a particular style (none, fixed, flex, row, or row container).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKRHKWpO0_qSb6MmwcOpwbz32Gh1mivWBAm0w7IgA8FljdSCNk-P7pstI0AqYImCL47XKF9bPDd-X321Uw7ZnSF_IgTL0S3ENfjc0sXshYgoBWTlP86LWXezemeXW69rmM_qwAx8UlVpVY/s1600/3sections.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="680" data-original-width="1391" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKRHKWpO0_qSb6MmwcOpwbz32Gh1mivWBAm0w7IgA8FljdSCNk-P7pstI0AqYImCL47XKF9bPDd-X321Uw7ZnSF_IgTL0S3ENfjc0sXshYgoBWTlP86LWXezemeXW69rmM_qwAx8UlVpVY/s320/3sections.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR26OM9QFopbishQWvE17oiqTqiBBACG_TbQ2aJAhT69NhU4oiiIROucbS-5pTrSFywLEnP1IgDsXb_rt4S2l2QyC9t_rgWy4VndEaIBYufLHmZRsS6HfvQGJdiU5Z5DB2g0WhF63Ueijk/s1600/3sections_theme.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="678" data-original-width="1423" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR26OM9QFopbishQWvE17oiqTqiBBACG_TbQ2aJAhT69NhU4oiiIROucbS-5pTrSFywLEnP1IgDsXb_rt4S2l2QyC9t_rgWy4VndEaIBYufLHmZRsS6HfvQGJdiU5Z5DB2g0WhF63Ueijk/s320/3sections_theme.png" width="320" /></a></div>
<br />
<br />
While working on a POC I came across a potential requirement to add an additional page section, and thought I'd investigate how difficult it would be. Why would you need to do such a crazy thing? Aren't 3 sections enough for everyone? I'm sure there are many reasons, but the one I envisaged was having a "flex" (ie full width) section between the header and main content for something like a banner, while the rest of the main content is fixed width. I'd say this is pretty common on a lot of sites, so worth some investigation. Yes it can be done purely through using the 'main' placeholder, but it's a lot more effort and probably requires your content authors to know a bit about containers.<br />
<br />
<blockquote>Recommended reading: <a href="https://doc.sitecore.com/developers/sxa/19/sitecore-experience-accelerator/en/create-a-custom-grid.html">creating a custom grid</a> - if you want to start a new grid from scratch (then come back here) </blockquote>
<br />
First step: figure out where the sections header, main, and footer are coming from.<br />
For those familiar with SXA, you'll know you can find these listed in the fields for your theme item. You can inspect the fields for this template (/sitecore/templates/Foundation/Experience Accelerator/Theming/Theme) to find the query it's using<br />
<code>/sitecore/system/Settings/Foundation/#Experience Accelerator#/Theming/Enums/Placeholders/*||query:/sitecore/templates/Foundation/#Experience Accelerator#/Grid/#Grid Definition#/#Placeholder Styles#/*</code><br />
or just do a quick search for 'header' or 'footer' , which works out to be (in my case, 9.2 or 9.3)<br />
<code>/sitecore/system/Settings/Foundation/Experience Accelerator/Theming/Enums/Placeholders</code><br />
<br />
Nice, let's add a new enum in here called 'banner'.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBol2gGaY3fFxpMFuGpnVIU9fvBS4NHKVjbbQJ6OSVOsgKJBo03qQk0EJwOENAIPW67iq65qcjx0y_b6MDViElDdvaB4XWlIWe_uUP3blwbdWmR9EGD_-R0c7rSW1FVSV0nZAvWZ4_gUA/s1600/banner_enum.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="523" data-original-width="1429" height="117" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBol2gGaY3fFxpMFuGpnVIU9fvBS4NHKVjbbQJ6OSVOsgKJBo03qQk0EJwOENAIPW67iq65qcjx0y_b6MDViElDdvaB4XWlIWe_uUP3blwbdWmR9EGD_-R0c7rSW1FVSV0nZAvWZ4_gUA/s320/banner_enum.png" width="320" /></a></div>
<br />
To make use of this new enum, go back to the theme and update the Placeholders field to include your new enum and style.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipe0bFcpWnZ-ROJ3f_gaiUhJCjg4vnp04a6lvGRzgjxr0zXaucrs3hv7FaZDRapM2YIc9GwjAqtrslHDUfHChCrEqBBKumrXx97ZzXEciBFHrUpY1Be948QfbAEM6L43o1S0Hod5U9G6oo/s1600/4sections.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="578" data-original-width="1421" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipe0bFcpWnZ-ROJ3f_gaiUhJCjg4vnp04a6lvGRzgjxr0zXaucrs3hv7FaZDRapM2YIc9GwjAqtrslHDUfHChCrEqBBKumrXx97ZzXEciBFHrUpY1Be948QfbAEM6L43o1S0Hod5U9G6oo/s320/4sections.png" width="320" /></a></div>
<br />
In an ideal world it would be this easy, but unfortunately SXA leaves one more painful (and difficult to find) step for us: the grid layout cshtml. In my case I'm using Bootstrap 4, so this is \Views\SxaLayout\Bootstrap4Body.cshtml. Inserting the new placeholder is easy enough:<br />
<pre><main>
@Html.Sitecore().Placeholder("banner")
<div id="content" class="@Html.Sxa().GridPlaceholderClasses("main")">
@Html.Sitecore().Placeholder("main")
</div>
</main></pre>
<br />
Now to save you a bit of a search, this file is referenced in /sitecore/system/Settings/Feature/Experience Accelerator/Bootstrap 4/Bootstrap 4 Grid Definition, which is then referenced by /sitecore/system/Settings/Feature/Experience Accelerator/Bootstrap 4/Grid Setup which is referenced in your site root node under Modules.<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2YAsJ2aRHDupQjI0tURZtlx7buyWjDhh_PCWCYQrfWxyF6SpIt7S57_3kY7zj-2xfEXbjwO1TDQ-0pwNT9jkzgW3mp1j4oSYoalVEMgKoxLpntYZVpdyIhrrqbibgQ7sZ1VSwXCsAZerr/s1600/bootstrap4gridsetup.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="583" data-original-width="1429" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2YAsJ2aRHDupQjI0tURZtlx7buyWjDhh_PCWCYQrfWxyF6SpIt7S57_3kY7zj-2xfEXbjwO1TDQ-0pwNT9jkzgW3mp1j4oSYoalVEMgKoxLpntYZVpdyIhrrqbibgQ7sZ1VSwXCsAZerr/s320/bootstrap4gridsetup.png" width="320" /></a></div>
<br />
We want to ensure we're not changing any out-of-the-box SXA items, so duplicate the /sitecore/system/Settings/Feature/Experience Accelerator/Bootstrap 4 folder (up a level so it's just under Feature) and give it a different name and display name so that you know your references are pointing to your custom one. Don't forget to point your Grid Setup item to your new grid definition.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9KXvVj30VomPpF4tpo2E5MMBbShLwKYEpbY9n9LwSpT8USgQJjVf-Prz7yfRJO-ywJ4ZY7mi0ZXeIKbEhSbxnkJvzKeATN_CtDZJ0KzlWmCs-N683q6yaXYW48UEymK-wmdu6k1if0FdQ/s1600/TestBootstrap4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="582" data-original-width="1429" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9KXvVj30VomPpF4tpo2E5MMBbShLwKYEpbY9n9LwSpT8USgQJjVf-Prz7yfRJO-ywJ4ZY7mi0ZXeIKbEhSbxnkJvzKeATN_CtDZJ0KzlWmCs-N683q6yaXYW48UEymK-wmdu6k1if0FdQ/s320/TestBootstrap4.png" width="320" /></a></div>
<br />
<br />
Back in the site root item Modules field add the custom item, move it up to the same position as the existing Boostrap 4 one so that any dependency ordering is kept intact, and remove the existing one.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi__geHSCfgY-aLYu2O2z8wE9ELdjvXSVuN2SdCAHhP4Wr5B63-deB1QDgmwVIhjHC9VGc9in-FS26tFiuOuthxvvfVkxCOuh6Bi_lBkQzl-pQExEr6vL5Gy1HJP72gD9WjXGloKX3jm6yD/s1600/SiteModules.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="583" data-original-width="1429" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi__geHSCfgY-aLYu2O2z8wE9ELdjvXSVuN2SdCAHhP4Wr5B63-deB1QDgmwVIhjHC9VGc9in-FS26tFiuOuthxvvfVkxCOuh6Bi_lBkQzl-pQExEr6vL5Gy1HJP72gD9WjXGloKX3jm6yD/s320/SiteModules.png" width="320" /></a></div>
<br />
<br />
Last but not least, the final hidden piece: in the Settings item under your site you'll find a "Grid Mapping" field which you'll want to update to the new grid.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3noImxRHMAEw_GiyRLOrRZasLVCZtebyOhorKnjdLMn0XeGMJqDJk1BkJa9_xKUHGtt1erwOH-7hRXD0cImD2TwAVpGDYAZTNvT7tCLfDtFDJ0myMuuUa4XnpPtZP7I4NjqE1LMQMFihn/s1600/SiteSettings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="214" data-original-width="1412" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3noImxRHMAEw_GiyRLOrRZasLVCZtebyOhorKnjdLMn0XeGMJqDJk1BkJa9_xKUHGtt1erwOH-7hRXD0cImD2TwAVpGDYAZTNvT7tCLfDtFDJ0myMuuUa4XnpPtZP7I4NjqE1LMQMFihn/s320/SiteSettings.png" width="320" /></a></div>
<br />
<br />
Boom! New full page width section where we can chuck our banners.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggwmijn0mscvaCKWGt33VO6wO-kA_bq3R1ANB3MxHlP3qlaDs7MJ41rdlumFAsA7yPt8mu6QGbWFkcQHFxzEJ_IhTnYwJfTbRzkLcG8UBYPDC83xmW2-ClxAJnD5Xf8KZDYqxNExfIGEXI/s1600/4SectionsDone.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="726" data-original-width="1429" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggwmijn0mscvaCKWGt33VO6wO-kA_bq3R1ANB3MxHlP3qlaDs7MJ41rdlumFAsA7yPt8mu6QGbWFkcQHFxzEJ_IhTnYwJfTbRzkLcG8UBYPDC83xmW2-ClxAJnD5Xf8KZDYqxNExfIGEXI/s320/4SectionsDone.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0tag:blogger.com,1999:blog-4822576408409428417.post-76337853235178359752019-08-09T15:51:00.002+10:002019-08-14T14:55:05.989+10:00SIF Distributed Installation notes/fixesI recently went through a SIF distributed installation for 9.1.1 and noticed quite a few things which were either broken, not included, or undocumented, so I thought I'd share my notes in case someone runs in to the same situation (which I'm sure will happen sooner or later).<br />
<br />
<h3>
The broken</h3>
Let's start with things that actually make SIF/Sitecore just plain not run successfully:<br />
<ul>
<li>The distributed templates have not been updated for 9.1.1, so even when downloading the <a data-survey_popup_delay="0" data-survey_url="" href="https://dev.sitecore.net/~/media/7255CF98254347108E085DFBB6687E02.ashx" target="_self">Sitecore Remote Distributed Deployment SIF Templates</a> file from the <a href="http://a chick here's daughter's name is Arya">9.1.1 downloads page</a> you will need to update the package names in the <i>XP1-Distributed.ps1</i> file from 9.1.0 to 9.1.1, and Identity from 2.0.0 to 2.0.1</li>
<li>In <i>XP1-Distributed.ps1</i> "SolrRoot" is defined but not passed through to the json file</li>
<li>In <i>xconnect-xp1-MarketingAutomation.json</i> "XConnectCollectionSearchService" should be "XConnectCollectionService" (in a couple of places), which is the parameter which is actually correctly passed through from the parent json</li>
<li>Unlike in the local SIF install, passwords and keys are not automatically generated if they are not provided. Your DB passwords and API keys will all be set to use "SIF-Default" which is the default value for all the parameters in the json. I'm putting this under "broken" since Sitecore logs multiple errors with a reporting API key of this value. </li>
<li>Even though the distributed install is set to install xConnect collection and xConnect collection search on different servers, it has not configured the necessary connection strings for separate servers per <a href="https://doc.sitecore.com/developers/91/platform-administration-and-architecture/en/configure-connections-strings.html#https://doc.sitecore.com/developers/91/platform-administration-and-architecture/en/configure-connections-strings.htmlUUID-88e640b8-2521-8ac7-1170-d15c82a4d468_setting-up-dedicated-search-and-collection-connection-strings">Setting up dedicated search and collection connection strings</a> in the documentation. If you're getting the following error from Experience Profile this is what's been missed
<pre>ERROR [Sitecore Services]: HTTP POST
URL https://sitecorecms/sitecore/api/ao/v1/contacts/search?&pageSize=20&pageNumber=1&sort=visitCount desc&Match=*&FromDate=03%2F03%2F2019&ToDate=02%2F04%2F2019
Exception System.NullReferenceException: Object reference not set to an instance of an object.
at Sitecore.Cintel.Endpoint.Plumbing.NegotiateLanguageFilter.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
at System.Web.Http.Filters.ActionFilterAttribute.OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
... etc...</pre></li>
</ul>
<h3>
The missing</h3>
<ul>
<li> While the packages for the DDS instance and CM DDS patch packages and json files are included, they are not incorporated into the <i>XP1-Distributed.ps1</i> or <i>XM1-Distributed.ps1</i></li>
<ul>
<li>In the .ps1 define the following:
<pre class="brush:powershell">$DdsComputerName = ""
$DdsSiteName = "$prefix.dds"
$DdsPackage = (Get-ChildItem "$SCInstallRoot\Sitecore 9.1.1 rev. * (OnPrem)_dds.scwdp.zip").FullName
$PatchPackage = (Get-ChildItem "$SCInstallRoot\Sitecore.Patch.EXM (OnPrem)_CM.zip").FullName
$ReportingServiceApiKey = ""
$EXMCryptographicKey = ""
$EXMAuthenticationKey = ""
$EXMInternalApiKey = ""
</pre>
and in the DistributedDeploymentParams add:
<pre class="brush:powershell">DdsComputerName = $DdsComputerName
DdsUserName = $HostsUserName
DdsPassword = $HostsUserPassword
DdsPackage = $DdsPackage
DdsSitename = $DdsSitename
PatchPackage = $PatchPackage
ReportingServiceApiKey = $ReportingServiceApiKey
EXMCryptographicKey = $EXMCryptographicKey
EXMAuthenticationKey = $EXMAuthenticationKey
EXMInternalApiKey = $EXMInternalApiKey</pre>
</li>
<li>Add the DDS and patch sections to the json (<a href="https://gist.github.com/moo2u2/8bdba170b3ee0a00f15fcfdd445d98bd">gist of full json</a>). The main additions are:
<pre class="brush:json">
"DdsConfig": ".\\sitecore-xp1-dds.json",
"DdsResourceFiles": [ "[variable('DdsConfig')]" , "[parameter('DdsPackage')]", "[parameter('LicenseFile')]", "[variable('CertGenerationConfig')]" ],
"Dds:ResourceFiles": "[concat(variable('RepResourceFiles'), variable('XConnectCertImportResourceFiles'))]",
"Dds:RemoteResourceFolder": "[variable('RemoteResourceFolder')]",
"Dds:ImportCertificatesParameters": "[variable('XConnectCertImportConfigurationParameters')]",
"Dds:GenerateCertificatesParameters": {
"Path": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:variable('CertGenerationConfig'),Leaf:true))]",
"CertificateName": "[parameter('DdsComputerName')]"
},
"Dds:ConfigurationParameters": {
"Path": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:variable('DdsConfig'),Leaf:true))]",
"Package": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:parameter('DdsPackage'),Leaf:true))]",
"SiteName": "[parameter('DdsSitename')]",
"XConnectCert": "[parameter('CertificateName')]",
"ProcessingService": "[parameter('ProcessingService')]",
"ReportingService": "[parameter('ReportingService')]",
"XConnectCollectionSearchService": "[parameter('XConnectCollectionSearchService')]",
"XConnectReferenceDataService": "[parameter('XConnectReferenceDataService')]",
"MarketingAutomationOperationsService": "[parameter('MarketingAutomationOperationsService')]",
"MarketingAutomationReportingService": "[parameter('MarketingAutomationReportingService')]",
"SitecoreIdentitySecret": "[parameter('ClientSecret')]",
"SitecoreIdentityAuthority": "[parameter('SitecoreIdentityAuthority')]",
"SolrUrl": "[parameter('SolrUrl')]",
"SolrCorePrefix": "[parameter('Prefix')]",
"SqlDbPrefix": "[parameter('Prefix')]",
"SqlServer": "[parameter('SqlServer')]",
"SqlAdminUser": "[parameter('SqlAdminUser')]",
"SqlAdminPassword": "[parameter('SqlAdminPassword')]",
"LicenseFile": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:parameter('LicenseFile'),Leaf:true))]",
"HostMappingName": "",
"DNSName": "[parameter('DdsComputerName')]",
"SSLCert": "[parameter('DdsComputerName')]",
"ExmEdsProvider": "CustomSMTP",
"ReportingServiceApiKey": "[parameter('ReportingServiceApiKey')]",
"EXMCryptographicKey": "[parameter('EXMCryptographicKey')]",
"EXMAuthenticationKey": "[parameter('EXMAuthenticationKey')]",
"EXMInternalApiKey": "[parameter('EXMInternalApiKey')]"
},
"PatchConfig": ".\\sitecore-XP1-cm-dds-patch.json",
"PatchResourceFiles": [ "[variable('PatchConfig')]" , "[parameter('PatchPackage')]", "[parameter('LicenseFile')]", "[variable('CertGenerationConfig')]" ],
"Patch:ResourceFiles": "[concat(variable('PatchResourceFiles'), variable('XConnectCertImportResourceFiles'))]",
"Patch:RemoteResourceFolder": "[variable('RemoteResourceFolder')]",
"Patch:ImportCertificatesParameters": "[variable('XConnectCertImportConfigurationParameters')]",
"Patch:GenerateCertificatesParameters": {
"Path": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:variable('CertGenerationConfig'),Leaf:true))]",
"CertificateName": "[parameter('CMComputerName')]"
},
"Patch:ConfigurationParameters": {
"Path": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:variable('PatchConfig'),Leaf:true))]",
"Package": "[JoinPath(variable('RemoteResourceFolder'), SplitPath(Path:parameter('PatchPackage'),Leaf:true))]",
"SiteName": "[parameter('CMSitename')]",
"EXMCryptographicKey": "[parameter('EXMCryptographicKey')]",
"EXMAuthenticationKey": "[parameter('EXMAuthenticationKey')]",
"EXMInternalApiKey": "[parameter('EXMInternalApiKey')]",
"DedicatedServerHostName": "[parameter('DdsComputerName')]"
}
</pre>
</li>
</ul>
</ul>
<h3>
The undocumented</h3>
<ul>
<li>The WinRM is using <code>UseSSL:true</code> which means a cert needs to be installed and port 5986 opened. See blog post <a href="http://thebitsthatbyte.com/winrm-over-https-for-a-sitecore-9-1-sif-distributed-installation/">WinRM over HTTPS for a Sitecore 9.1 SIF Distributed Installation</a> for more details.</li>
<li>In our case the default WinRM <i>MaxEnvelopeSizekb</i> was tiny and needed to be increased for the Sitecore file sizes.<br /><code>winrm set winrm/config @{MaxEnvelopeSizekb="8192"}</code></li>
</ul>
<h3>
Minor others</h3>
<ul>
<li>Despite allowing you to set the install files path in the ps1 the <i>RemoteResourceFolder</i> path <code>C:\ResourceFiles\</code> is hardcoded everywhere else</li>
<li>There are quite a few cases of the wrong description for fields (a case of copy/paste I believe), eg. in <i>xconnect-ip1-MarketingAutomation.json </i>the description for <i>XConnectReferenceDataService</i> should say "Reference Data" but says "Collection Search"</li>
</ul>
Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com1tag:blogger.com,1999:blog-4822576408409428417.post-69516529546511448812019-05-31T14:58:00.001+10:002020-02-21T11:19:43.282+11:00Sitecore Azure Search suggestions<b>TL;DR</b> https://github.com/moo2u2/Sitecore-Azure-Search-Suggestions<br />
<br />
One of the more common requests I hear (and quite obvious gap in my opinion) is the support for suggestions when using Azure Search. Auto-suggestions are <a href="https://doc.sitecore.com/developers/91/platform-administration-and-architecture/en/using-solr-auto-suggest.html">supported when using Solr</a> as of 9.0.1 (and SXA supports this in the <a href="https://doc.sitecore.com/users/sxa/18/sitecore-experience-accelerator/en/walkthrough--adding-search-functionality-to-your-page.html">Search Box</a> rendering parameters) and Azure has both <a href="https://docs.microsoft.com/en-us/rest/api/searchservice/suggestions">Suggestions</a> and <a href="https://docs.microsoft.com/en-us/rest/api/searchservice/autocomplete">Autocomplete</a> APIs.<br />
<br />
I recently had the opportunity to take a crack at this missing feature, which I based off the SXA implementation.<br />
<br />
Code is in my <a href="https://github.com/moo2u2/Sitecore-Azure-Search-Suggestions">GitHub Sitecore-AzureSearch-Suggestions repo.</a><br />
There are 3 branches:<br />
<ul>
<li>The <b><i>master</i></b> branch contains the simplest implementation, however uses Azure Search binaries from NuGet, and has a dependency on SXA.</li>
<li>The <b><i>no-azure-client</i></b> branch does not use the Azure Search binaries but makes API calls directly like the rest of the Sitecore Azure Search implementation</li>
<li>The <i><b>no-sxa</b> </i>branch contains the implementation <i>with</i> Azure binaries, but <i>no</i> SXA dependency</li>
</ul>
There is also an Autocomplete implementation in there (partial in some branches). For a quick overview of the difference see this <a href="https://azure.microsoft.com/en-au/blog/autocomplete-in-azure-search-now-in-public-preview/">MS blog post</a>.<br />
<br />
Unfortunately the Sitecore Azure Search dlls (<code>Sitecore.ContentSearch.Azure.*</code>) do not seem to be as extensible as the rest of sitecore, as there are numerous internal classes and private methods/properties.<br />
<br />
The following classes had to pretty much be copied out as they couldn't be extended :( <br />
<ul>
<li><code>Sitecore.ContentSearch.Azure.Http.SearchService</code></li>
<li><code>Sitecore.ContentSearch.Azure.Http.SearchServiceClient</code> properties + <code>GetClient</code> method</li>
<li><code>Sitecore.ContentSearch.Azure.Http.CompositeSearchService</code></li>
<li><code>Sitecore.ContentSearch.Azure.CloudSearchProviderIndex</code> <code>ConnectionStringName,</code></li>
<li><code>SearchCloudIndexName</code> properties</li>
<li><code>Sitecore.ContentSearch.Azure.CloudSearchProviderIndexName</code></li>
<li><code>Sitecore.ContentSearch.Azure.ISwitchSearchIndexInitializable</code></li>
<li><code>Sitecore.ContentSearch.Azure.Schema.CloudSearchIndexSchema</code></li>
<li><code>Sitecore.ContentSearch.Azure.Http.MultiStatusResponseDocument</code></li>
<li>All <code>Sitecore.ContentSearch.Azure.Http.Exceptions</code> exceptions</li>
<li><code>Sitecore.ContentSearch.Azure.Exception.CloudSearchCompositeSearchServiceException</code></li>
<li><code>Sitecore.ContentSearch.Azure.Exception.CloudSearchMissingImplementationException</code></li>
</ul>
Hopefully the product team can fix this up for a later version! <br />
<br />
Oh and I found a couple of typos while I was in there ;)<br />
<ul>
<li><code>Sitecore.ContentSearch.Azure.Utils.Retryer.I<b>Rerty</b>Policy</code></li>
<li>/sitecore/media library/Base Themes/SearchTheme/Scripts/component-search-box<br /><code>return '<div class="<b>sugesstion</b>-item">' + suggestionText + '</div>';</code></li>
</ul>
Jason Woodshttp://www.blogger.com/profile/12584524854965809897noreply@blogger.com0