<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sean G. Wright</title>
    <description>The latest articles on Forem by Sean G. Wright (@seangwright).</description>
    <link>https://forem.com/seangwright</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F152179%2F1a3ce219-703d-46cb-b473-dcadd80152f4.jpg</url>
      <title>Forem: Sean G. Wright</title>
      <link>https://forem.com/seangwright</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/seangwright"/>
    <language>en</language>
    <item>
      <title>Kentico Xperience Design Patterns: Page Type Restrictions</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 03 Apr 2023 11:48:14 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-page-type-restrictions-419j</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-page-type-restrictions-419j</guid>
      <description>&lt;p&gt;In previous posts we looked at how we can model content with Page Types and Content Hierarchies.&lt;/p&gt;

&lt;p&gt;In this post we'll continue our content tree architecture exploration 🗺️ by learning about Page Type restrictions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is Part 3 of 4 in a series on Content Tree Architecture in Kentico Xperience 13. You can read &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-page-type-modeling-50b8"&gt;Part 1&lt;/a&gt;, &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-content-hierarchies-5e9o"&gt;Part 2&lt;/a&gt;, or Part 4.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Limitations are Liberating&lt;/li&gt;
&lt;li&gt;Allowed Page Types&lt;/li&gt;
&lt;li&gt;Video Library Example&lt;/li&gt;
&lt;li&gt;
Recommendations

&lt;ul&gt;
&lt;li&gt;Always Limit Allowed Page Types&lt;/li&gt;
&lt;li&gt;Clean Up Allowed Page Types&lt;/li&gt;
&lt;li&gt;Disable Linked Pages&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations are Liberating
&lt;/h2&gt;

&lt;p&gt;Since we'll be discussing "restrictions" in this post, we should first touch on a mantra of of mine that explains why I find Page Type Restrictions so valuable 💰 in a Content Tree Architecture.&lt;/p&gt;

&lt;p&gt;Variety might be &lt;a href="https://www.merriam-webster.com/dictionary/variety%20is%20the%20spice%20of%20life"&gt;the spice 🌶️ of life&lt;/a&gt;, but too much can lead to &lt;a href="https://www.scientificamerican.com/article/the-tyranny-of-choice/"&gt;the tyranny of choice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-98YaeN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7aacqsyvar9qccqzv2bp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-98YaeN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7aacqsyvar9qccqzv2bp.png" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;a href="http://baumchevybuick.blogspot.com/2014/02/did-you-know-breakfast-cereal.html"&gt;Image Credit&lt;/a&gt;



&lt;p&gt;We want to have choices 😀, but to make the best decisions, in a reasonable amount of time, we need to have the right options to choose from - not every option 🧠.&lt;/p&gt;

&lt;p&gt;This idea is directly applicable to the work that marketers and content managers do with a &lt;a href="https://www.gartner.com/reviews/market/digital-experience-platforms"&gt;DXP&lt;/a&gt; like Kentico Xperience. It gives them many tools and features to accomplish their goals, which is why they love the platform 🤩.&lt;/p&gt;

&lt;p&gt;However, if a product has many features and all of them are always available, it can be hard to identify which features are available intentionally and which are incidental to a flexible and powerful product 🤔. We (developers) want the teams we are supporting (marketers, content managers, administrators) to &lt;a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/"&gt;fall into the pit of success&lt;/a&gt; 🤗 - curating experiences by limiting choices really helps with this 👍🏾.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allowed Page Types
&lt;/h2&gt;

&lt;p&gt;What's the point of setting up a consistent, scalable content architecture if we can't help marketers and content managers keep it well organized over the long term 🤷🏻?&lt;/p&gt;

&lt;p&gt;One of the best tools to enforcing good organizational practices and guiding those responsible for the site into "the pit of success" is &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create"&gt;Allowed Page Types&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can limit where pages of a specific type can be created in the Content Tree, from the perspective of the parent &lt;em&gt;and&lt;/em&gt; the child 🧐.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Library Example
&lt;/h2&gt;

&lt;p&gt;In the screenshot below, we can see the "Allowed parent page types" for the Video Library Page Type - it can only be created under the Root page ✅.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LfXQ05rN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgta3zjtbta8n2hhyulx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LfXQ05rN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgta3zjtbta8n2hhyulx.png" alt="Video Library allowed page types settings" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also see that Video Library pages can only have 2 types of child pages - the Call To Action Container and Videos Container.&lt;/p&gt;

&lt;p&gt;The Video Container Page Type also has restrictions - it can only be created under a Video Library and can only have Video Year children:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mg9SuDIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsehf8uyk418qgs7nqe3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mg9SuDIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsehf8uyk418qgs7nqe3.png" alt="Video Container allowed page types settings" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Video Year and Month follow the same pattern - the Year can only be created under the Video Container with only Months as children and the Month can only be created under the Year with Videos as children 😉:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zaLSOR07--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com%250A/uploads/articles/uk7rn6r40k6k0v3xs116.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zaLSOR07--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com%250A/uploads/articles/uk7rn6r40k6k0v3xs116.png" alt="Video Year allowed page types settings" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D3hQoari--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21c04e9a3iww82jj5qqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D3hQoari--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21c04e9a3iww82jj5qqx.png" alt="Video Month allowed page types settings" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we get to the Video Page Type and see it is the end of the content hierarchy. It can only be created under Video Months and cannot have any children:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T5Q46ATH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcvev2fg9p7rlnltfnbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T5Q46ATH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcvev2fg9p7rlnltfnbt.png" alt="Video allowed page types settings" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What this means in practice is the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Videos are only found as children of a Year/Month hierarchy in a "Video content" container inside a Video Library.&lt;/li&gt;
&lt;li&gt;Administrators of the site don't need to think 😩 about which type of page they can create under Video Containers, Video Years, Video Months, or Videos. Xperience automatically selects the only valid option 🙏🏽, if there is one.&lt;/li&gt;
&lt;li&gt;Anyone responsible for managing content in the site can quickly learn 🤓 exactly where to go to find that content, even if there are multiple Video Libraries.&lt;/li&gt;
&lt;li&gt;As the number of Videos grows, we don't have to fight against the organization of other types of content (like CTAs) because they have their own organization with container Page Types 😅.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Always Limit Allowed Page Types
&lt;/h3&gt;

&lt;p&gt;We should always limit the Allowed Parent/Child Page Types &lt;em&gt;as much as possible&lt;/em&gt;. If we find that a Page Type needs to have many different parent and child pages, it &lt;em&gt;might&lt;/em&gt; mean that page is serving too many roles 👈 in the content strategy and we should try to create separate (but maybe similar) Page Types to serve those roles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are exceptions to this, like a "Basic Page" that has no structured content and acts as a landing page in multiple areas of a site. But even these types of pages should have their Allowed Page Types limited ✔️.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A strictly organized content hierarchy can always be relaxed over time if the requirements of the site change, but it is very difficult to "put the genie 🧞‍♂️ back in the bottle" and create a sensible organization out of a content tree that's been poorly maintained 🏚️ for years.&lt;/p&gt;

&lt;p&gt;It's worth also noting that just because a Page Type only allows a single type of child Page Type we can't also assume that child Page Type can only be placed under that parent 😦.&lt;/p&gt;

&lt;p&gt;The Page Type restrictions need to be setup on both ends to be really affective. The parent should have a limited number of child types and the child should have a limited number (often 1) of parent types.&lt;/p&gt;

&lt;p&gt;This can be easy to forget, so we should double check 📝 before we deploy our Page Types to a production environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Up Allowed Page Types
&lt;/h3&gt;

&lt;p&gt;Additionally, if the requirements of the site allow for a single Video Library (ex: we don't need a Video Library per product line) we can create the Video Library page at the root of the site and &lt;em&gt;then&lt;/em&gt; remove the Root page type from its Allowed Parent page types 🤔.&lt;/p&gt;

&lt;p&gt;This has the benefit of ensuring only 1 Video Library will ever exist &lt;em&gt;and&lt;/em&gt; reduces the options anyone has when creating a new page at the root of the site. "Landing Page" might appear in that list, but "Video Library" never will 😎.&lt;/p&gt;

&lt;p&gt;We should give administrators the tools they need to not only do their job, but do it well 👏🏻, by helping them focus on the work that is impactful - not making decisions about things they don't want to do anyway 👍🏾.&lt;/p&gt;

&lt;p&gt;In the screenshot below we can see that I've removed the Allowed parent and child Page Types for the Video Library page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EdK8_RqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hykzpefjnk4pncp3k8n3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EdK8_RqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hykzpefjnk4pncp3k8n3.png" alt="Video Library Page Type - Allowed Page Types configuration" width="733" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if we try to create a new page under the Video Library in the Content Tree, we can see the application limits us and provides a helpful message explaining why we cannot create any pages here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mv4cYGof--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8o6r9hnccd3al86oo8jg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mv4cYGof--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8o6r9hnccd3al86oo8jg.png" alt="'Cannot create new page' message in the Pages application" width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we'll see later, this is helpful in creating and organizing "Content Libraries" in Kentico Xperience 13 🧐.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disable Linked Pages
&lt;/h3&gt;

&lt;p&gt;Kentico Xperience 13 has a &lt;a href="https://docs.xperience.io/managing-website-content/working-with-pages/copying-and-moving-pages-creating-linked-pages#Copyingandmovingpages,creatinglinkedpages-Creatinglinkedpages"&gt;Linked Pages&lt;/a&gt; feature that enables creating a copy of a page to another location in the Content Tree without copying any of its content 🤨.&lt;/p&gt;

&lt;p&gt;I have used this feature in some very specific situations, but in general I avoid it. Duplicating pages can cause confusion related to where content of this Page Type &lt;em&gt;should&lt;/em&gt; be created and there are many other ways to share and reuse content on a site. Linked pages also &lt;a href="https://docs.xperience.io/developing-websites/content-modeling-guide/storing-content-in-xperience#StoringcontentinXperience-Contenttreestructure"&gt;have some limitations&lt;/a&gt; 🫤 that we should be aware of if we decide to use them.&lt;/p&gt;

&lt;p&gt;Disabling Linked Pages globally in the Content Tree is pretty simple. We first need to navigate to the &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create#Limitingthepagesuserscancreate-Specifyingwhichpagesuserscancreateundercertainpaths"&gt;Page Type Scopes&lt;/a&gt; application and then create a new Scope:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FaQKgNc0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfz6an0r1ii0m5l317dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FaQKgNc0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfz6an0r1ii0m5l317dz.png" alt="Page Type Scopes application" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our new Scope should cover the root of the site &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;Apply to the whole section&lt;/code&gt;, which means all children for the entire Content Tree. We don't want to use this scope to limit which Page Types we want to use (we can do that with other Scopes), so we select &lt;code&gt;Allow all page types&lt;/code&gt;. We will leave &lt;code&gt;Allow creation a linked page&lt;/code&gt; unchecked, which will disable Linked Pages for the Content Tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--foOjK13f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oc9fxdwuyqoi77cd8t5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--foOjK13f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oc9fxdwuyqoi77cd8t5b.png" alt="Creating a new Page Type Scope to limit Linked Pages" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we want to opt-in to Linked Pages somewhere else in the Content Tree, we can create a new Scope for that section of the tree and enable &lt;code&gt;Allow creating a linked page&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The net effect of all this setup is that page creation rules follow the guidance of our Allowed Page Type configuration and our content can be found in predictable locations 🙏🏼.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While the Allowed Page Types feature in Xperience might be simple, it's an important and powerful 💪🏽 tool that helps us keep content organized. Well organized content ensures we'll know where to create new content and we'll be able to govern the content we've already created.&lt;/p&gt;

&lt;p&gt;Allowed Page Type configuration is usually dependent on our sitemap, content models, and content architecture. We want to use it to limit choices for marketers and content managers so they only have to choose between options that make sense 😊.&lt;/p&gt;

&lt;p&gt;Setting up our initial Allowed Page Types should be followed by removing some allowed types after our page structure has been created in the Content Tree - further reducing options and confusion.&lt;/p&gt;

&lt;p&gt;Finally, we might want to consider disabling Linked Pages everywhere except in the specific locations where it is needed.&lt;/p&gt;

&lt;p&gt;Remember 🧐, a highly organized Content Tree can be made more flexible if needed, but it's very difficult to enforce structure on one that has been disorganized 🤕 for a long time.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.merriam-webster.com/dictionary/variety%20is%20the%20spice%20of%20life"&gt;Webster: Variety is the spice of life&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scientificamerican.com/article/the-tyranny-of-choice/"&gt;Scientific American: The Tyranny of Choice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gartner.com/reviews/market/digital-experience-platforms"&gt;Gartner: Digital Experience Platform (DXP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/"&gt;Coding Horror: The Pit of Success&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create"&gt;Xperience Docs: Allowed Page Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/managing-website-content/working-with-pages/copying-and-moving-pages-creating-linked-pages#Copyingandmovingpages,creatinglinkedpages-Creatinglinkedpages"&gt;Xperience Docs: Linked Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/content-modeling-guide/storing-content-in-xperience#StoringcontentinXperience-Contenttreestructure"&gt;Xperience Docs: Content Modeling - Linked Pages limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create#Limitingthepagesuserscancreate-Specifyingwhichpagesuserscancreateundercertainpaths"&gt;Xperience Docs: Page Type Scopes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;






&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>content</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Content Hierarchies</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Fri, 03 Mar 2023 15:55:10 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-content-hierarchies-5e9o</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-content-hierarchies-5e9o</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-page-type-modeling-50b8"&gt;previous post&lt;/a&gt; we looked at how we can model 🗿 content with Page Types and learned about the value of content governance 🧑🏽‍🏫.&lt;/p&gt;

&lt;p&gt;In this post we'll continue our Content Tree Architecture exploration by learning about Content Hierarchies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is Part 2 of 4 in a series on Content Tree Architecture in Kentico Xperience 13. You can read &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-page-type-modeling-50b8"&gt;Part 1&lt;/a&gt;, &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-page-type-restrictions-419j"&gt;Part 3&lt;/a&gt;, or Part 4.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Parent-Child Relationships&lt;/li&gt;
&lt;li&gt;Groups and Containers&lt;/li&gt;
&lt;li&gt;Hierarchy and URLs&lt;/li&gt;
&lt;li&gt;
Recommendations

&lt;ul&gt;
&lt;li&gt;Planning&lt;/li&gt;
&lt;li&gt;Naming&lt;/li&gt;
&lt;li&gt;URLs&lt;/li&gt;
&lt;li&gt;More Page Types, not Fewer&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parent-Child Relationships
&lt;/h2&gt;

&lt;p&gt;Content models, when combined with sitemaps, reveal a natural hierarchy of content 🤔.&lt;/p&gt;

&lt;p&gt;As two examples, a Video Library landing page likely has child Video pages and a Product Line page usually has child Product pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4z6h80wqucvnike444m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4z6h80wqucvnike444m.png" alt="A sitemap with a parent-child hierarchy" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This list-detail pattern is very common in content hierarchies but it's not the only one 😯.&lt;/p&gt;

&lt;p&gt;A site might have an Office Location page for each regional office. The Office Location pages could then have a child page for each employee at that office. The Office Location page might not show all the employees working at that location, but it can make sense to organize the employees as children of the Office Location anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fla6zxt707qbuwha41bll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fla6zxt707qbuwha41bll.png" alt="Multiple levels of parent-child hierarchy in a sitemap" width="800" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this parent-child relationship in mind, we might be inspired to go and create parent and child Page Types for every content model for our site. There's nothing wrong with this approach, but there is also a better alternative 😎.&lt;/p&gt;

&lt;h2&gt;
  
  
  Groups and Containers
&lt;/h2&gt;

&lt;p&gt;As we already noted, a Video Library landing page is very likely to have child Video pages somewhere in its descendant page hierarchy. These could be direct descendants as seen in this screenshot of the Xperience Content Tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jvvpiamxdwk7ppqyhdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jvvpiamxdwk7ppqyhdf.png" alt="Content Tree with a simple parent-child relationship" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As our content structure and site matures, an issue we will encounter with the direct parent-child structure is there's no good place to add other types of structured content 😔.&lt;/p&gt;

&lt;p&gt;What if we want to create some structured Call To Action pages that are placed on the Video Library page with the Page Builder? Where would those go in this example 🤷🏻? We could add them as direct children of the Video Library next to the Videos, but then as the number of Video pages grows, the Call To Action pages will get lost in the list 😧.&lt;/p&gt;

&lt;p&gt;Imagine another scenario where we have 5 Video pages being created every business day. By the end of the year there could be nearly 1,300 Video Pages in the Video Library - finding a specific Video could be difficult 😫, especially if we're using the &lt;a href="https://docs.xperience.io/developing-websites/page-builder-development/selectors-for-page-builder-components" rel="noopener noreferrer"&gt;Page Selector&lt;/a&gt; to select a Video page to feature somewhere else on the site in the Page Builder.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This rate of content growth would also result in more child pages than Xperience &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure#Definingwebsitecontentstructure-Limitationsofusingpagesforstoringcontent" rel="noopener noreferrer"&gt;recommends for the Content Tree&lt;/a&gt; - "each item (page) in the content tree have at most 1000 direct child pages."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better approach, which takes a little more setup, is to create organizational Page Types and insert those under the Video Library page - all child content would then be contained in the organizational Page Types 🤔:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81a89op9lj9h1mi4zuqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81a89op9lj9h1mi4zuqt.png" alt="Content Tree with more complex hierarchy using organizational pages" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above we can see there are several new pages in the Content Tree (Page Name - Page Type):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Videos - Video Container&lt;/li&gt;
&lt;li&gt;2022/2023 - Video Year&lt;/li&gt;
&lt;li&gt;01/02 - Video Month&lt;/li&gt;
&lt;li&gt;CTAs - Call To Action Container&lt;/li&gt;
&lt;li&gt;Try Our Coffee For the Full Experience - Call To Action&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If your are interested in trying out a prescriptive approach to creating these container pages, check out fellow MVP Mike Wills' &lt;a href="https://github.com/heywills/page-asset-folder-module" rel="noopener noreferrer"&gt;Page Asset Folder Module&lt;/a&gt; library 👍🏼.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hierarchy and URLs
&lt;/h2&gt;

&lt;p&gt;In the past, we often needed to consider the impact that the Content Tree structure would have on the URLs generated for each page. Adding too much structure could lead to overly verbose URLs and the need for aliases and URL shorteners 🛠️.&lt;/p&gt;

&lt;p&gt;Thankfully, Kentico Xperience 13's Content Tree based routing system is more nuanced and easily customizable. It's up to us, when creating Page Types, to indicate whether a Page Type influences the generated URLs.&lt;/p&gt;

&lt;p&gt;Organizational or container Page Types &lt;em&gt;can&lt;/em&gt; &lt;a href="https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing#Contenttreebasedrouting-PageURLgeneration" rel="noopener noreferrer"&gt;have the URL feature disabled&lt;/a&gt;, which means their existence isn't reflected in the Live site URL structure - they exist purely to make content governance easier for marketers 💪🏻.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgikxjt5anfmnbfb9uri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgikxjt5anfmnbfb9uri.png" alt="Visual diagram showing the impact of the Page Type URL feature on generated URLs" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;
From the Xperience documentation



&lt;p&gt;In more advanced scenarios, this added content structure can be leveraged to add &lt;a href="https://docs.xperience.io/managing-users/configuring-permissions/configuring-page-permissions/page-level-permissions-acls" rel="noopener noreferrer"&gt;Page-level access control lists&lt;/a&gt; (ACLs) to what content is visible to users. This applies to both visitors to the Live site and content creators in the Admin application 🙌🏾.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5weboglyxmt13ejggzih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5weboglyxmt13ejggzih.png" alt="Content Tree ACL management" width="577" height="786"&gt;&lt;/a&gt;&lt;/p&gt;
From the Xperience documentation



&lt;p&gt;The disconnection of Live site URLs and content structure from the Content Tree lets us tune content organization to meet content governance needs while also prioritizing visitor experiences 🤓.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Planning
&lt;/h3&gt;

&lt;p&gt;Generally, we model content with Page Type fields but we also need to model content relationships.&lt;/p&gt;

&lt;p&gt;A well thought out (and understood) content hierarchy will help our sites scale both in the amount of content (thousands of videos, as an example) and future content usability. What features or new types of content will we need to add that haven't even been imagined yet 🧐!?&lt;/p&gt;

&lt;p&gt;The Xperience documentation has &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure#Definingwebsitecontentstructure-Settingupcontenttreestructureforalargenumberofpages" rel="noopener noreferrer"&gt;specific guidance for sites with a large number of pages&lt;/a&gt; in the Content Tree and it aligns with what has been presented in this post.&lt;/p&gt;

&lt;p&gt;Ok, so we have some guidelines... but how do those apply to &lt;em&gt;our&lt;/em&gt; site's content and requirements? It might not be clear what the best approach is - for developers or content managers 🤷‍♀️.&lt;/p&gt;

&lt;p&gt;I recommend planning a few different content structures. Talk with stakeholders about this plan and then demo the Content Tree variations in the Xperience Admin so they can &lt;em&gt;see&lt;/em&gt; 👀 an implementation of the abstract concepts.&lt;/p&gt;

&lt;p&gt;Don't get too caught up generating all the Page Type fields or code. We don't even need the Live site running at this point, because the goal is to have a visible Content Tree and identify any areas where scalability, requirements, or complexity haven't been resolved 📝.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming
&lt;/h3&gt;

&lt;p&gt;We should try to define a &lt;a href="https://seangwright.github.io/kentico-xperience-13-style-guide/guide/page-types.html#use-a-naming-convention-for-page-types" rel="noopener noreferrer"&gt;naming convention for Page Types&lt;/a&gt; to better represent how a Page Type is being used, because with dozens of landing, structured content, and organizational Page Types, things can get confusing 🥴 if we aren't intentional.&lt;/p&gt;

&lt;p&gt;Not only are the names of Page Types reflected in the Administration application (primarily in the Content Tree), but also in the application code.&lt;/p&gt;

&lt;p&gt;Our goal should be to align the names of Page Types with how stakeholders (content managers and marketers) would name them so we have a &lt;a href="https://www.martinfowler.com/bliki/UbiquitousLanguage.html" rel="noopener noreferrer"&gt;ubiquitous language&lt;/a&gt; 🧠 across teams. We also want to help developers be able to make safe assumptions about their use when they see them referenced in code 🤓.&lt;/p&gt;

&lt;p&gt;Personally, I like using the conventions below, but yours don't have to be identical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suffixing Page Types with the &lt;a href="https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing#Contenttreebasedrouting-PageURLgeneration" rel="noopener noreferrer"&gt;URL feature enabled&lt;/a&gt; with &lt;code&gt;Page&lt;/code&gt; (ex: &lt;code&gt;HomePage&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Suffixing Page Types that is reusable structured content with &lt;code&gt;Content&lt;/code&gt; (ex: &lt;code&gt;NavigationContent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Suffixing Page Types that are organizational or are less important than their children with &lt;code&gt;Group&lt;/code&gt; (ex: &lt;code&gt;CTAGroup&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You can read more about Page Type naming conventions in the &lt;a href="https://seangwright.github.io/kentico-xperience-13-style-guide/guide/page-types.html#use-a-naming-convention-for-page-types" rel="noopener noreferrer"&gt;Kentico Xperience 13 Style Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  URLs
&lt;/h3&gt;

&lt;p&gt;It's worth pointing out &lt;em&gt;again&lt;/em&gt; that the robust content organization we've been reviewing doesn't have to affect our URLs since Xperience allows us to enable or disable the URL feature for each Page Type 🙂. &lt;/p&gt;

&lt;p&gt;Disabling it for an organizational Page Type means &lt;a href="https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing#Contenttreebasedrouting-PageURLgeneration" rel="noopener noreferrer"&gt;it won't participate in URL generation&lt;/a&gt; for any child pages that do have the URL feature enabled. This lets us organize content as needed while also ensuring our URLs and content structure match the project planning sitemap ✔️.&lt;/p&gt;

&lt;p&gt;This is a feature that's new to Kentico Xperience 13 and can free content managers from the tyranny of the "Content Tree is Sitemap" mentality 😒 (we'll touch on this idea again in a later post).&lt;/p&gt;

&lt;p&gt;Try exploring it to see how you can improve content governance without negatively affecting the live site experience for visitors.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Page Types, not Fewer
&lt;/h3&gt;

&lt;p&gt;It's extremely important to note that in the Video Library example we reviewed earlier, each of the organizational Page Types is a &lt;em&gt;different&lt;/em&gt; Page Type. Although we could create a generic "Container" or "Folder" Page Type that can organize anything, this approach would lose 😬 one of the most important benefits of these organizational Page Types.&lt;/p&gt;

&lt;p&gt;As we will see in a later post, the &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create#Limitingthepagesuserscancreate-Allowinguserstoplacecertainpagesunderapagetype" rel="noopener noreferrer"&gt;Allowed Page Types&lt;/a&gt; feature in Xperience is extremely powerful and can lead to amazing content governance, but it requires a broader set of Page Types to leverage fully 🤔.&lt;/p&gt;

&lt;p&gt;In general, we shouldn't feel discouraged from creating more Page Types. We should create as many as we need to help ensure a well structured and easily governable content tree 👍🏽.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Modeling content is an extremely important step in implementing an effective and maintainable website. But content in Xperience isn't only the individual structured fields of a page - it's also the relationships between those pages. This relationship modeling can be achieved through the organization of content hierarchies in the Content Tree 🧐.&lt;/p&gt;

&lt;p&gt;There's also an aspect of content governance that benefits 😇from a well structured Content Tree in a Kentico Xperience site.&lt;/p&gt;

&lt;p&gt;Adding layers of abstraction to the Content Tree can lead to a more manageable site, long term. It can make it easier to find content and result in a more intuitive experience for content managers 🤗. Thanks to Xperience's flexible URL feature, we don't have to sacrifice live site URLs or visitor experience for these administration improvements.&lt;/p&gt;

&lt;p&gt;These benefits don't come for free. We will need to spend time planning the Content Tree structure after identifying our content models and pages in the visual sitemap. Creating a sample Content Tree structure can help in this planning process 🗺️ by turning the abstract into something more concrete for stakeholders without too much work.&lt;/p&gt;

&lt;p&gt;In the end, content organization is one of the tools we can invest in to help content managers and marketers with the work they do customizing a site's experiences, every day.&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/page-builder-development/selectors-for-page-builder-components" rel="noopener noreferrer"&gt;Xperience Docs: Page Builder Components - Page Selector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure#Definingwebsitecontentstructure-Limitationsofusingpagesforstoringcontent" rel="noopener noreferrer"&gt;Xperience Docs: Content Tree Limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/heywills/page-asset-folder-module" rel="noopener noreferrer"&gt;GitHub: Mike Wills' Page Asset Folder Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing#Contenttreebasedrouting-PageURLgeneration" rel="noopener noreferrer"&gt;Xperience Docs: Page URL Generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/managing-users/configuring-permissions/configuring-page-permissions/page-level-permissions-acls" rel="noopener noreferrer"&gt;Xperience Docs: Page ACLs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure#Definingwebsitecontentstructure-Settingupcontenttreestructureforalargenumberofpages" rel="noopener noreferrer"&gt;Xperience Docs: Guidance for Large Content Trees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://seangwright.github.io/kentico-xperience-13-style-guide/guide/page-types.html#use-a-naming-convention-for-page-types" rel="noopener noreferrer"&gt;Kentico Xperience 13 Style Guide: Page Type naming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.martinfowler.com/bliki/UbiquitousLanguage.html" rel="noopener noreferrer"&gt;Martin Fowler: Ubiquitous Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types/limiting-the-pages-users-can-create#Limitingthepagesuserscancreate-Allowinguserstoplacecertainpagesunderapagetype" rel="noopener noreferrer"&gt;Xperience Docs: Allowed Page Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/implementing-routing/content-tree-based-routing" rel="noopener noreferrer"&gt;Xperience Docs: Content Tree Routing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



Photo by &lt;a href="https://unsplash.com/@jordanmadrid" rel="noopener noreferrer"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;






&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md" rel="noopener noreferrer"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>content</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Page Type Modeling</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Wed, 08 Feb 2023 17:15:08 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-page-type-modeling-50b8</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-page-type-modeling-50b8</guid>
      <description>&lt;p&gt;Designing and organizing content with Kentico Xperience 13 has foundations going back over a decade. &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types"&gt;Page Types&lt;/a&gt; and the &lt;a href="https://docs.xperience.io/13tutorial/business-tutorial/web-content-management/using-the-pages-application#UsingthePagesapplication-AccessingthePagesapplication"&gt;Content Tree&lt;/a&gt; have existed, in some form or another, for a long time.&lt;/p&gt;

&lt;p&gt;These features' longevity is a testament to their design and flexibility, but let's not remain stuck in the past 👴🏽. Instead, let's look at what opportunities they enable for better content creation and governance in Kentico Xperience 13 and beyond 🤓.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is Part 1 of 4 in a series on Content Tree Architecture in Kentico Xperience 13. You can read &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-content-hierarchies-5e9o"&gt;Part 2&lt;/a&gt;, &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-page-type-restrictions-419j"&gt;Part 3&lt;/a&gt;, or Part 4 (when published).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;History of Page Type Modeling&lt;/li&gt;
&lt;li&gt;Content Governance&lt;/li&gt;
&lt;li&gt;Modeling With Page Types&lt;/li&gt;
&lt;li&gt;
Recommendations

&lt;ul&gt;
&lt;li&gt;
Which Page Types Do We Create?&lt;/li&gt;
&lt;li&gt;Naming Things&lt;/li&gt;
&lt;li&gt;Container Page Types&lt;/li&gt;
&lt;li&gt;Types of Fields&lt;/li&gt;
&lt;li&gt;Improving the Experience&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  History of Page Type Modeling
&lt;/h2&gt;

&lt;p&gt;Traditionally, Kentico sites using the previous versions' &lt;a href="https://docs.xperience.io/k12sptutorial?devModel=pe"&gt;Portal Engine technology&lt;/a&gt; were commonly built with "Page (menu item)" pages, which often represented much of the content structure of a site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CCg4gRwm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v35du82vpxh5bbkqxd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CCg4gRwm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6v35du82vpxh5bbkqxd1.png" alt="Classic Page (menu item) Page Type field definition" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This Page Type was also often used for content tree organization. Pages that weren't ever meant to be navigated to or viewed would have URL and template features, just because it was simpler and faster to what was already there 🤷.&lt;/p&gt;

&lt;p&gt;When the Page (menu item) seemed a bit much for content organization, there was also a built-in Folder Page Type that could be used (or abused ☠️) to contain just about anything in the Content Tree.&lt;/p&gt;

&lt;p&gt;One of the many caveats of the Folder was that its name impacted the URL of its child pages. This meant internal content organization always had an impact on the live site 😔.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SAWMRXO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccjdpirs5miliin585xg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SAWMRXO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccjdpirs5miliin585xg.png" alt="Class Folder Page Type definition" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There were some common outliers like Products (SKUs) because Kentico provided the SKU structured content fields for product information. The Media Library and Blog Posts also provided some out-of-the-box structured content.&lt;/p&gt;

&lt;p&gt;However, &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/choosing-the-format-of-page-content"&gt;structured content&lt;/a&gt; wasn't as common as it is today because rich text was king 🤴, the web was the primary (and often only) channel, and a lot of content was authored inline in a page's design.&lt;/p&gt;

&lt;p&gt;I don't want it to seem like no one used structured content (many teams used it heavily with great success 👍🏾). It's just that Kentico didn't necessarily encourage or discourage its use - it left it up to implementors to use whatever they wanted (often what they were handed with a newly created site).&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Governance
&lt;/h2&gt;

&lt;p&gt;Content managers and marketers have learned that while authoring in rich text is simpler for content creation, it leads to poor &lt;a href="https://18f.gsa.gov/2021/07/27/content_governance_what_it_is_and_how_to_get_started/"&gt;content governance&lt;/a&gt; 😫.&lt;/p&gt;

&lt;p&gt;Using rich text for authoring content might be a great solution for a marketing campaign landing page or &lt;a href="https://www.clearvoice.com/blog/what-is-pillar-content/"&gt;pillar content&lt;/a&gt; because it's not meant to be reused elsewhere on the site or other channels.&lt;/p&gt;

&lt;p&gt;But, a product catalog authored in rich text is going to be a nightmare 😵 for content reuse and modification after the first week.&lt;/p&gt;

&lt;p&gt;If we move in the direction of structured content (and keep our &lt;a href="https://www.youtube.com/watch?v=b7cYeIL1ZcU"&gt;content separate from the design&lt;/a&gt;) we'll set ourselves up for success in content strategy, reuse, and governance. &lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types"&gt;Page Types&lt;/a&gt; are the most important starting point to achieve these goals 🤩.&lt;/p&gt;

&lt;p&gt;Content authoring and content governance are always going to be competing priorities - often making one easier will make the other more difficult. This isn't always the case, but we need to remember 🧠 there is no perfect solution for every situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling With Page Types
&lt;/h2&gt;

&lt;p&gt;Before we begin creating Page Types we want to identify all the content that will appear on the site and &lt;a href="https://kontent.ai/learn/tutorials/set-up-kontent-ai/content-modeling/what-is-content-modeling/introduction-to-content-modeling/"&gt;model it&lt;/a&gt; by defining each field or unit of content and also the way the information in that field can be organized or stored.&lt;/p&gt;

&lt;p&gt;This content is often informed by &lt;a href="https://xd.adobe.com/ideas/process/information-architecture/visual-sitemap-examples-website-designs/"&gt;visual sitemaps&lt;/a&gt; and a &lt;a href="https://www.greenmellenmedia.com/what-is-a-content-outline-for-a-website/"&gt;content outline&lt;/a&gt; 🧐. If we don't have either of these to work from, we'll find it's hard to define a goal for modeling with Page Types 😕.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PSTy8Pjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.com/blog/wp-content/uploads/2019/11/screen_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PSTy8Pjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.com/blog/wp-content/uploads/2019/11/screen_1.png" alt="A sitemap with a parent-child hierarchy" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;
A sample visual sitemap by &lt;a href="https://miro.com/blog/wp-content/uploads/2019/11/screen_1.png"&gt;Miro&lt;/a&gt;






&lt;p&gt;If we look at the Kentico Xperience 13 &lt;a href="https://docs.xperience.io/installation/quick-installation"&gt;Dancing Goat sample site&lt;/a&gt; and navigate to the Article Page Type definition, we can see it has several fields that model the content of articles on the site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JIXnefep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipln7r619yzh6eeiz4xm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JIXnefep--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipln7r619yzh6eeiz4xm.png" alt="Page Type application showing the fields of the Article Page Type" width="800" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we compare this to the Cafe Page Type definition we can see that there is a different set of fields defined for the Cafe Page Type because the content model for a cafe is different than for an article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PqqpNV-N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0ze0r4kg7m2m93udd3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PqqpNV-N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0ze0r4kg7m2m93udd3k.png" alt="Page Type application showing the fields of the Cafe Page Type" width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see in the screenshot below how these two types of Pages are organized in the Content Tree - each type is grouped under a common parent. This could be because the sitemap requires it, for organization (more on this in a later post), or both.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X3rMEUe1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khkxjwr41h8cr73ivhw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X3rMEUe1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khkxjwr41h8cr73ivhw5.png" alt="Article and Cafe Pages in the Content Tree" width="705" height="1188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Page Types model aspects of the visual sitemap and content from the content outline, both defined in the planning phase for a site. Translating from these sources of information into the features available with Xperience's Page Types is an &lt;em&gt;important step&lt;/em&gt; in designing a site that supports and grows with an organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which Page Types Do We Create?
&lt;/h3&gt;

&lt;p&gt;Figuring out what &lt;em&gt;is&lt;/em&gt; and &lt;em&gt;is not&lt;/em&gt; part of each Page Type is key. Creating a new set of custom Page Types for every site should be a normal part of our workflow. The majority of our content should &lt;em&gt;not&lt;/em&gt; be a catch-all "Page (menu item)" Page Type. Instead, aim towards creating unique custom Page Types with structured content fields 😎.&lt;/p&gt;

&lt;p&gt;From my experiences (and those of other MVPs), having 30-60 custom Page Types is normal. This number might seem very large, especially if we don't have that many content models, but as we'll see in later posts, Page Types don't just model content, they also help with content organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming Things
&lt;/h3&gt;

&lt;p&gt;While we might try to use structured content as much as possible, we'll still have a basic "Page" that acts as an aggregation point in our site for structured content found elsewhere (more on this in a later post). I typically name the catch-all Page Type "Basic Content Page" because it doesn't have much structured content (a title, primary image, and some metadata).&lt;/p&gt;

&lt;p&gt;That said, as developers, we know that naming things is difficult 😅.&lt;/p&gt;

&lt;p&gt;Naming things that administrators interact with - even more so - and we want to use names that make sense to them. So, provide them several options to pick from that you think make sense and explain how a Page Type will be used. In 6 years when no one likes the name selected, everyone can have a laugh about it 😆.&lt;/p&gt;

&lt;p&gt;Fortunately, some &lt;a href="https://seangwright.github.io/kentico-xperience-13-style-guide/guide/page-types.html#naming"&gt;guidance around naming with Page Types&lt;/a&gt; has already been collected in the evolving Kentico Xperience 13 Style Guide 👍🏽.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Page Types
&lt;/h3&gt;

&lt;p&gt;As an aside, I would recommend &lt;em&gt;against&lt;/em&gt; creating creating Page Types without custom fields. I'm all for creating "folder" or "container" Page Types (as we'll see in later posts), but the negatives of Pages without custom fields (not being able to add custom fields &lt;em&gt;ever&lt;/em&gt;) far outweigh the cons of having fields but not needing them just yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw47XxPP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irs5ma30j27qckvegtqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw47XxPP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irs5ma30j27qckvegtqv.png" alt="Creating a container Page Type with custom fields" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xperience requires us to add at least 1 custom field, so if we don't have any field to add when the Page Type is created, we can create a Boolean field that isn't required or displayed in the editing form. We can name it "PlaceholderField" to make it clear it's not being used. &lt;/p&gt;

&lt;p&gt;Administrators will never see it and we can delete it when actually have custom fields to add in the future 🤗.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hLouLBX_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewbe6lv3l2oq3wvbahzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hLouLBX_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewbe6lv3l2oq3wvbahzm.png" alt="Creating a place holder custom field for a container Page Type" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Fields
&lt;/h3&gt;

&lt;p&gt;A decision we'll often have to make when modeling Page Types is "what is the best way to store this information?"&lt;/p&gt;

&lt;p&gt;In general I'd recommend against storing integer IDs of objects in Xperience because these aren't stable between environments and require special handling by the site when preparing for content staging. Instead use &lt;code&gt;Guid&lt;/code&gt; values because they can be guaranteed to be the same in every environment assuming all content gets created in the same environment before being synced to the next 💪🏼.&lt;/p&gt;

&lt;p&gt;A decision that bit me several times over the years was using the "Boolean (yes/no)" field type when the content or data I'm trying to model isn't truly binary 🤨.&lt;/p&gt;

&lt;p&gt;If we have a Page Type named "Article" and we want to represent if an article is "Premium" or not (can only be accessed by logged in users), we might be tempted to make this a Boolean field - &lt;code&gt;ArticleIsPremium&lt;/code&gt;. This will give content managers a simple checkbox in the Page form. If "true" it's premium, otherwise it's standard 🙂.&lt;/p&gt;

&lt;p&gt;However, what happens when 6 months later we need to represent if an article is "Limited"? Do we add another Boolean field - &lt;code&gt;ArticleIsLimited&lt;/code&gt;? What happens when an article needs to be either limited, premium, or standard but not multiple 😒.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-categories"&gt;Categories&lt;/a&gt; might be a good choice here, but they don't allow us to limit selection to only 1 Category.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's become clear that "premium" isn't a true/false state of an article. It's one of multiple values for a "catalog level" of an article. Boolean fields are convenient but they lock us into only ever having 2 values.&lt;/p&gt;

&lt;p&gt;The trick is to learn to identify when content is actually binary and when it currently has 2 possible values, but isn't inherently binary 😉!&lt;/p&gt;

&lt;p&gt;Instead of using a Boolean field, I'd recommend using a standard Text field with the Drop-down list with a List of options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--te22cblX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhln8ojah1ezvj15gz03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--te22cblX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhln8ojah1ezvj15gz03.png" alt="Page Type field using a Drop-down list" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even if you start out with only 2 options, this approach to modeling the content is far more scalable 🤓.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving the Experience
&lt;/h3&gt;

&lt;p&gt;A Page Type might model a single type of content that has multiple variations or aspects. For example, an Article could have either a related Video or Image Gallery. Ideally, a content manager could select what type of related media an Article has and be presented with fields in the Page's Content form that are relevant to that type of media.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is another example of the previous topic of not limiting ourselves to Boolean fields.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://docs.xperience.io/custom-development/extending-the-administration-interface/developing-form-controls/reference-field-editor"&gt;Field advanced settings&lt;/a&gt; section of the Page Type field's form contains some very powerful 🤘🏿 functionality in the "depending fields" and "visibility condition" settings.&lt;/p&gt;

&lt;p&gt;We can use these to deliver a better content authoring and management experience by conditionally showing or hiding fields.&lt;/p&gt;

&lt;p&gt;For example, the Image Gallery fields for an Article only appear in the form if the "Image Gallery" option was selected as the type of media for an Article 🥳.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As we've seen, there's a lot of depth to the process of creating Page Types that model our content and sitemap accurately.&lt;/p&gt;

&lt;p&gt;Content authoring and governance can sometimes be at odds with each other. Creating the right Page Types isn't just about identifying types of content. It's also knowing the organization goals of a site. Naming things and the content management experience are important and require developers to collaborate with site content managers and marketers 😎.&lt;/p&gt;

&lt;p&gt;We &lt;em&gt;can&lt;/em&gt; identify some tips for content modeling with Page Types, but because each site's content is different, it's a process with outcomes that are unique to each site.&lt;/p&gt;

&lt;p&gt;Throughout this series, I'll be linking to &lt;a href="https://docs.xperience.io/developing-websites/content-modeling-guide"&gt;Content Modeling Guide&lt;/a&gt; for Kentico Xperience 13.&lt;/p&gt;

&lt;p&gt;It's a great resource that helps developers think more strategically 🧐 about building their sites and not focus &lt;em&gt;only&lt;/em&gt; on the technical details.&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/managing-page-types"&gt;Xperience Docs: Page Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/13tutorial/business-tutorial/web-content-management/using-the-pages-application#UsingthePagesapplication-AccessingthePagesapplication"&gt;Xperience Docs: Content Tree&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/k12sptutorial?devModel=pe"&gt;Kentico Docs: Portal Engine technology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/defining-website-content-structure/choosing-the-format-of-page-content"&gt;Xperience Docs: Structured Content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://18f.gsa.gov/2021/07/27/content_governance_what_it_is_and_how_to_get_started/"&gt;Content Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.clearvoice.com/blog/what-is-pillar-content/"&gt;Pillar Content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=b7cYeIL1ZcU"&gt;YouTube: Kentico Connection 2021 - Content Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kontent.ai/learn/tutorials/set-up-kontent-ai/content-modeling/what-is-content-modeling/introduction-to-content-modeling/"&gt;Kontent.ai Docs: Introduction to Content Modeling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/installation/quick-installation"&gt;Xperience Docs: Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://seangwright.github.io/kentico-xperience-13-style-guide/guide/page-types.html#naming"&gt;Kentico Xperience 13 Style Guide: Naming Page Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-categories"&gt;Xperience Docs: Categories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/custom-development/extending-the-administration-interface/developing-form-controls/reference-field-editor"&gt;Xperience Docs: Field advanced settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/content-modeling-guide"&gt;Xperience Docs: Content Modeling Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;






&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>content</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Exploring Xperience By Kentico: Introductions</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Wed, 19 Oct 2022 17:20:43 +0000</pubDate>
      <link>https://forem.com/seangwright/exploring-xperience-by-kentico-introductions-li9</link>
      <guid>https://forem.com/seangwright/exploring-xperience-by-kentico-introductions-li9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In this post we'll be exploring &lt;a href="https://docs.xperience.io/xp" rel="noopener noreferrer"&gt;Xperience by Kentico&lt;/a&gt;. A high performant and ASP.NET Core based Digital Experience Platform (DXP).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧭 Starting Our Journey: What's a DXP?
&lt;/h2&gt;

&lt;p&gt;Most developers are familiar with the term Content Management System (CMS) and might have worked with one at some point in their career 🙂.&lt;/p&gt;

&lt;p&gt;CMS applications allow content managers to author and manage content (typically pages or media).&lt;/p&gt;

&lt;p&gt;Digital Experience Platforms (DXP) are similar because they include content management features. However, they also include a key focus on the experience of visitors on a site and how they interact and engage with content and media 😮. This focus is supported through digital marketing and commerce features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kentico has a good explanation of &lt;a href="https://www.kentico.com/discover/dxp-hub" rel="noopener noreferrer"&gt;the difference between a CMS and DXP&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🗺 Making a Path: Setting Up Xperience By Kentico
&lt;/h2&gt;

&lt;p&gt;Now that we understand what a DXP is, let's look at how we can get started with Kentico's DXP - Xperience by Kentico.&lt;/p&gt;

&lt;p&gt;First things first, if you want to follow along you'll need to setup Xperience by Kentico locally on your machine. Fortunately it's pretty easy 👏🏽!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience" rel="noopener noreferrer"&gt;setup documentation&lt;/a&gt; is straight forward and driven entirely by .NET tooling we've come to love - the &lt;code&gt;dotnet&lt;/code&gt; CLI and NuGet packages.&lt;/p&gt;

&lt;p&gt;We will need a valid license during the setup process and we can acquire one by signing up for an account in the &lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience/licenses#Licenses-EvaluateXperience" rel="noopener noreferrer"&gt;Kentico Client Portal&lt;/a&gt; and generating a new evaluation license (it's really simple!).&lt;/p&gt;

&lt;p&gt;There's a full &lt;a href="https://docs.xperience.io/tutorial/developer-tutorial/xperience-by-kentico-overview" rel="noopener noreferrer"&gt;developer tutorial&lt;/a&gt; which walks us through setup, configuration, content modeling, and page creation 🧠.&lt;/p&gt;

&lt;p&gt;I've already created several demo projects and I can say with 100% confidence that this product is ready to explore and try (even if it doesn't have all the features we might need for production).&lt;/p&gt;

&lt;p&gt;Some of the other Kentico MVPs have already &lt;a href="https://www.luminary.com/blog/xperience-by-kentico-technology-highlights" rel="noopener noreferrer"&gt;started&lt;/a&gt; &lt;a href="https://www.luminary.com/blog/next-generation-xperience-by-kentico" rel="noopener noreferrer"&gt;exploring&lt;/a&gt; and &lt;a href="https://www.goldfinch.me/blog/updating-to-the-latest-version-of-xperience-by-kentico/" rel="noopener noreferrer"&gt;building sites&lt;/a&gt; on Xperience by Kentico!&lt;/p&gt;

&lt;h2&gt;
  
  
  🌍 Bridging Two Worlds: The Return of the Single Application
&lt;/h2&gt;

&lt;p&gt;A big change in the overall architecture of Kentico Xperience applications came in Kentico 12 MVC, where the "Presentation" application (ASP.NET MVC 5) &lt;a href="https://docs.xperience.io/k12sp/developing-websites/mvc-development-overview" rel="noopener noreferrer"&gt;was separate&lt;/a&gt; from the "Administration" application (ASP.NET Web Forms).&lt;/p&gt;

&lt;p&gt;This separation allowed the Xperience product team to evolve the features of the &lt;a href="https://docs.xperience.io/k12sp/developing-websites/page-builder-development" rel="noopener noreferrer"&gt;Page Builder&lt;/a&gt; quickly 🏃🏾‍♀️ without being limited by the Web Forms technology of the administration application.&lt;/p&gt;

&lt;p&gt;This pattern continued with &lt;a href="https://docs.xperience.io/developing-websites/mvc-development-overview" rel="noopener noreferrer"&gt;Kentico Xperience 13&lt;/a&gt;, which now supports ASP.NET Core for the "Presentation" application while the administration application still runs on the well established ASP.NET Web Forms technology.&lt;/p&gt;

&lt;p&gt;With Xperience by Kentico we have a return to the classic single-application architecture we're (maybe 🤷🏽‍♀️) familiar with from years past. Since the administration has been rebuilt using the same ASP.NET Core technology that the presentation (live) site uses, they can be combined into a single application 🙌🏼.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iac3uad969txbqyr1e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iac3uad969txbqyr1e3.png" alt="Dancing Goat sample application code base project folder structure in VS Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, the administration application is delivered &lt;a href="https://docs.xperience.io/xp/developers-and-admins/development/website-development-basics#Websitedevelopmentbasics-Xperienceadministration" rel="noopener noreferrer"&gt;as a single NuGet package&lt;/a&gt; 🧐 that can be added to your ASP.NET Core presentation application.&lt;/p&gt;

&lt;p&gt;Not only is this a return to the convenient single-application model, but it's a far simpler architecture than any previous version of Kentico/Kentico Xperience 🤩.&lt;/p&gt;

&lt;p&gt;This is great for developers because it speeds up their ability to build and test custom features, and deploy the application. This is also a win for administrators and marketers because the technology is simpler to understand and there are fewer prickly "gotchas" 🌵 with a single application.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌞 Live Site Only Deployments
&lt;/h3&gt;

&lt;p&gt;The single application deployment model is great for simplicity, but what about when we want to scale out the "live" site that visitors actually see 🤔?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we have to scale out the administration functionality?&lt;/li&gt;
&lt;li&gt;If we want to limit exposure of the administration features for security do we have to deploy them to make the live site work?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While two applications did increase complexity, it also gave us an appealing answer to those two questions (hint: nope!)&lt;/p&gt;

&lt;p&gt;Fortunately, the folks at Kentico realized that some teams would want to opt-in to deployment complexity for the benefit of flexibility and made sure to support a &lt;a href="https://docs.xperience.io/x/SoXWCQ" rel="noopener noreferrer"&gt;dual application deployment strategy&lt;/a&gt; in Xperience by Kentico 🤓.&lt;/p&gt;

&lt;p&gt;From the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Xperience administration interface (installed via the Kentico.Xperience.Admin NuGet package) and all related customizations can be removed when publishing to production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Awesome 💪🏿!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎨 Delightful Designs
&lt;/h2&gt;

&lt;p&gt;The original spirit of the designs of the Web Forms administration application came about &lt;a href="https://docs.xperience.io/k8/using-the-kentico-interface" rel="noopener noreferrer"&gt;in Kentico 8&lt;/a&gt;, and while the UI has been refreshed since then, the core patterns are still in use in Kentico Xperience 13.&lt;/p&gt;

&lt;p&gt;Xperience by Kentico has taken another leap forward by completely rethinking the design of the administration interface from the ground up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frawv6ix7vi9ckgkh4sww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frawv6ix7vi9ckgkh4sww.png" alt="Xperience by Kentico home dashboard with application tiles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many of the same ideas are there, so it will quickly be familiar to anyone exposed to Kentico's products from the past several years, but the interface feels less cluttered, more intuitive, and more dynamic 🍃.&lt;/p&gt;

&lt;p&gt;Previously there were limitations on what could be accomplished with the design (and technology) path the administration application was on. Freed from those limitations, the product really shines and we get to see a strong, forward looking UX vision.&lt;/p&gt;

&lt;p&gt;These updates aren't limited to the navigation and dialogs that have been revamped across the UI. They can also be seen in the Page Builder which has evolved in some elegant ways when compared to Kentico Xperience 13.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F591zvkbma9hwj2ps1vsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F591zvkbma9hwj2ps1vsk.png" alt="Page Builder Widget properties dialog form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can even see the beginnings of a mobile-friendly administration UI. I know many of us don't plan to author content or administer a site on our phone 🤳🏻, but you never know when you might want to check on a marketing automation workflow or add a new user to a site while you're waiting in the checkout line at the store or at your kids' sporting event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkllz1eav88qw0i32ok8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkllz1eav88qw0i32ok8x.png" alt="Xperience by Kentico home dashboard in a mobile viewport"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's some additional screenshots of design changes in the product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjmyru1np55mv8ygctw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjmyru1np55mv8ygctw.png" alt="Form Builder dialog"&gt;&lt;/a&gt;&lt;/p&gt;
An updated Form Builder with a new &lt;a href="https://docs.xperience.io/xp/developers-and-admins/development/builders/form-builder/form-components" rel="noopener noreferrer"&gt;Form Component&lt;/a&gt; dialog



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3shwzhdo5s30my5wvwjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3shwzhdo5s30my5wvwjq.png" alt="Condition Builder dialog"&gt;&lt;/a&gt;&lt;/p&gt;
Fully featured marketing automation &lt;a href="https://docs.xperience.io/xp/business-users/digital-marketing/contact-groups#Contactgroups-Setupdynamiccontactgroups" rel="noopener noreferrer"&gt;Condition Builder for Contact Groups&lt;/a&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64hsfpowsdp1li2izwpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64hsfpowsdp1li2izwpc.png" alt="Media Libraries application UI"&gt;&lt;/a&gt;&lt;/p&gt;
Redesigned &lt;a href="https://docs.xperience.io/xp/business-users/media-libraries/manage-media-files" rel="noopener noreferrer"&gt;Media Libraries application&lt;/a&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd2ljfyzshnxkyj7xeib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd2ljfyzshnxkyj7xeib.png" alt="New Icon selection"&gt;&lt;/a&gt;&lt;/p&gt;
Brand new set of icons to associate to &lt;a href="https://docs.xperience.io/x/goHWCQ" rel="noopener noreferrer"&gt;Page Types&lt;/a&gt;, Page Builder components and other parts of the admin UI.


 

&lt;h2&gt;
  
  
  🚀 Fast and Modern - React and ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;The administration UI is now built on &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://dotnet.microsoft.com/en-us/learn/aspnet/what-is-aspnet-core" rel="noopener noreferrer"&gt;ASP.NET Core&lt;/a&gt; which means it's &lt;em&gt;fast&lt;/em&gt; 🚀!&lt;/p&gt;

&lt;p&gt;If you'd like to watch a video to see what it's like to use Xperience by Kentico, check out this one here:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/DV4XG6401qg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Since the administration UI is integrated into an ASP.NET Core through a NuGet package, it has very little impact on application startup time 🏁.&lt;/p&gt;

&lt;p&gt;This is great for developers when iterating through a code &amp;gt; build &amp;gt; test cycle, and it's great for stakeholders because a faster application means faster builds and deployments which enable faster time to market 📈 with new features and experiences!&lt;/p&gt;

&lt;p&gt;These new technologies might seem a little intimidating 🤯, especially if we're used to the no-code admin customizations of previous versions of the product.&lt;/p&gt;

&lt;p&gt;Thankfully, the new technology can be adopted gradually (as needed) and Kentico provides helpful 🤝🏽 guidance in setting up &lt;a href="https://docs.xperience.io/xp/developers-and-admins/customization/extend-the-administration-interface/prepare-your-environment-for-admin-development" rel="noopener noreferrer"&gt;our local development environment&lt;/a&gt; for Admin UI customizations.&lt;/p&gt;

&lt;p&gt;The docs also provide some detailed examples of working with React to build custom &lt;a href="https://docs.xperience.io/xp/developers-and-admins/customization/extend-the-administration-interface/ui-form-components#UIformcomponents-Formcomponentfrontend" rel="noopener noreferrer"&gt;Form Components&lt;/a&gt; which can be used to build any kind of administration experience we can dream up!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With the React-based libraries for customizing the Admin UI being delivered through npm, we can &lt;a href="https://unpkg.com/browse/@kentico/xperience-admin-components@22.2.3/package.json" rel="noopener noreferrer"&gt;explore the tools and dependencies&lt;/a&gt; used to build them 🧐.&lt;/p&gt;

&lt;p&gt;At the time of this writing, the product is using the latest version of React - 18.2. Nice 💪🏾!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📰 Always Fresh - Weekly Updates
&lt;/h2&gt;

&lt;p&gt;One of the most exciting changes for Xperience by Kentico, compared to all previous versions of Kentico's flagship products, is the speed of iteration!&lt;/p&gt;

&lt;p&gt;New versions of the Xperience by Kentico are being released nearly every month (with bug fixes being released every week).&lt;/p&gt;

&lt;p&gt;Because this breakneck pace 🏎 is a lot to keep up with, the documentation team has been maintaining &lt;a href="https://docs.xperience.io/xp/xperience-changelog" rel="noopener noreferrer"&gt;a changelog&lt;/a&gt; for all of us, so we can easily stay up to date with all the latest developments.&lt;/p&gt;

&lt;p&gt;Just take a look at the various versions of &lt;a href="https://www.nuget.org/packages/Kentico.Xperience.WebApp#versions-body-tab" rel="noopener noreferrer"&gt;NuGet packages&lt;/a&gt; that have been released over the past 3 months to get a good idea of the tempo Kentico has taken for this product.&lt;/p&gt;

&lt;p&gt;They are wasting no time 😅!&lt;/p&gt;

&lt;p&gt;Before you start wondering "If I decide to create and Xperience by Kentico project, how do I keep it up to date with all these new releases?", head on over to the docs where a full set of (fairly simple) &lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience/update-xperience-projects" rel="noopener noreferrer"&gt;instructions already exists&lt;/a&gt; to get your project on the latest version.&lt;/p&gt;

&lt;p&gt;Since Xperience by Kentico relies of standard .NET tooling and NuGet packages, keeping projects up-to-date is &lt;em&gt;far&lt;/em&gt; simpler than using the &lt;a href="https://docs.xperience.io/external-utilities/kentico-xperience-installation-manager" rel="noopener noreferrer"&gt;Kentico Installation Manager&lt;/a&gt; in the past.&lt;/p&gt;

&lt;p&gt;(It can even be automated and made part of our CI/CD process! 🤩)&lt;/p&gt;

&lt;h2&gt;
  
  
  ✈ Heading Home: What's Next for Xperience by Kentico?
&lt;/h2&gt;

&lt;p&gt;For most developers used to Kentico's previous products, the progress being made in Xperience by Kentico is both exciting 🥳 and intimidating 😬.&lt;/p&gt;

&lt;p&gt;One key question stands out - how do we get our existing applications onto Xperience by Kentico? Thankfully, Kentico is already working on that and has &lt;a href="https://docs.xperience.io/xp/developers-and-admins/kentico-xperience-13-migration" rel="noopener noreferrer"&gt;information about migrations&lt;/a&gt; in their documentation.&lt;/p&gt;

&lt;p&gt;Expect this migration guide to grow over time as new features are added to Xperience by Kentico and expect the &lt;a href="https://github.com/Kentico/xperience-migration-toolkit" rel="noopener noreferrer"&gt;migration toolkit repository&lt;/a&gt; to evolve as more use-cases are supported.&lt;/p&gt;

&lt;p&gt;So, what about those upcoming features? What are they?&lt;/p&gt;

&lt;p&gt;To find out, we just need to browse &lt;a href="https://portal.productboard.com/kenticoxperience/1-kentico-xperience-roadmap/tabs/4-xperience-by-kentico-next" rel="noopener noreferrer"&gt;Xperience by Kentico's roadmap 🗺&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Given the pace at which the team is iterating and enhancing this product, taking a quick glance at plans for the near future every few weeks is probably a good idea!&lt;/p&gt;

&lt;p&gt;Which parts of Xperience by Kentico are you most excited about?&lt;br&gt;
Have you tried it out yet?&lt;br&gt;
If not, what's your biggest roadblock?&lt;/p&gt;

&lt;p&gt;Answer in the comments below...&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;


Photo by &lt;a&gt;NASA
&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp" rel="noopener noreferrer"&gt;Xperience by Kentico docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kentico.com/discover/dxp-hub" rel="noopener noreferrer"&gt;Difference between CMS and DXP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience" rel="noopener noreferrer"&gt;Xperience by Kentico setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience/licenses#Licenses-EvaluateXperience" rel="noopener noreferrer"&gt;Evaluation License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/tutorial/developer-tutorial/xperience-by-kentico-overview" rel="noopener noreferrer"&gt;Xperience by Kentico developer tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.luminary.com/blog/xperience-by-kentico-technology-highlights" rel="noopener noreferrer"&gt;Xperience by Kentico Technology Highlights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.luminary.com/blog/next-generation-xperience-by-kentico" rel="noopener noreferrer"&gt;The next generation of Xperience by Kentico is coming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goldfinch.me/blog/updating-to-the-latest-version-of-xperience-by-kentico/" rel="noopener noreferrer"&gt;Updating to the latest version of Xperience by Kentico&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/k12sp/developing-websites/mvc-development-overview" rel="noopener noreferrer"&gt;Kentico 12 MVC development architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/k12sp/developing-websites/page-builder-development" rel="noopener noreferrer"&gt;Kentico 12 MVC Page Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/mvc-development-overview" rel="noopener noreferrer"&gt;Kentico Xperience 13 development architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/development/website-development-basics#Websitedevelopmentbasics-Xperienceadministration" rel="noopener noreferrer"&gt;Xperience by Kentico administration development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/x/SoXWCQ" rel="noopener noreferrer"&gt;Xperience by Kentico dual application deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/k8/using-the-kentico-interface" rel="noopener noreferrer"&gt;Kentico 8 user interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/en-us/learn/aspnet/what-is-aspnet-core" rel="noopener noreferrer"&gt;What is ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/customization/extend-the-administration-interface/prepare-your-environment-for-admin-development" rel="noopener noreferrer"&gt;Xperience by Kentico local environment setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/customization/extend-the-administration-interface/ui-form-components#UIformcomponents-Formcomponentfrontend" rel="noopener noreferrer"&gt;Xperience by Kentico React Form Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unpkg.com/browse/@kentico/xperience-admin-components@22.2.3/package.json" rel="noopener noreferrer"&gt;Xperience by Kentico @kentico/xperience-admin-components package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/xperience-changelog" rel="noopener noreferrer"&gt;Xperience by Kentico changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Kentico.Xperience.WebApp#versions-body-tab" rel="noopener noreferrer"&gt;Xperience by Kentico NuGet package versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/install-xperience/update-xperience-projects" rel="noopener noreferrer"&gt;Xperience by Kentico upgrade instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/external-utilities/kentico-xperience-installation-manager" rel="noopener noreferrer"&gt;Kentico Installation Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/xp/developers-and-admins/kentico-xperience-13-migration" rel="noopener noreferrer"&gt;Kentico Xperience 13 to Xperience by Kentico migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Kentico/xperience-migration-toolkit" rel="noopener noreferrer"&gt;Xperience by Kentico migration toolkit repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://portal.productboard.com/kenticoxperience/1-kentico-xperience-roadmap/tabs/4-xperience-by-kentico-next" rel="noopener noreferrer"&gt;Xperience by Kentico feature roadmap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md" rel="noopener noreferrer"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>aspnetcore</category>
      <category>react</category>
      <category>cms</category>
    </item>
    <item>
      <title>Bits of Xperience: Displaying Rich Text After a Form Submission</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Tue, 06 Sep 2022 12:54:54 +0000</pubDate>
      <link>https://forem.com/wiredviews/bits-of-xperience-displaying-rich-text-after-a-form-submission-144j</link>
      <guid>https://forem.com/wiredviews/bits-of-xperience-displaying-rich-text-after-a-form-submission-144j</guid>
      <description>&lt;p&gt;Kentico Xperience's &lt;a href="https://docs.xperience.io/managing-website-content/forms/composing-forms" rel="noopener noreferrer"&gt;Form Builder&lt;/a&gt; enables marketing professionals to quickly build and manage forms in a no-code UI for use across their website 👩🏾‍💼.&lt;/p&gt;

&lt;p&gt;The experience for site visitors submitting a form can be customized for successful submissions 👍🏽.&lt;/p&gt;

&lt;p&gt;Let's look at what's possible and how a few lines of customized code can extend the functionality even more 😮!&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What happens after a Form is submitted&lt;/li&gt;
&lt;li&gt;How to customize the Form management UI&lt;/li&gt;
&lt;li&gt;How to customize Razor Class Libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❓ What Happens After a Form is Submitted?
&lt;/h2&gt;

&lt;p&gt;Forms on a website are one of the most common ways for marketers to engage with visitors.&lt;/p&gt;

&lt;p&gt;Let's take the Dancing Goat demo site Contact Form as an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx5wm7t5o91e7qq632pg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx5wm7t5o91e7qq632pg.png" alt="Filled out Contact form on a website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a visitor fills out this Form and submits it, what exactly happens 🤔?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the form is not valid (based on &lt;a href="https://docs.xperience.io/managing-website-content/forms/composing-forms#Composingforms-Addingfieldvalidation" rel="noopener noreferrer"&gt;form field validation&lt;/a&gt;) Xperience will re-render the form with validation error UI and messages&lt;/li&gt;
&lt;li&gt;If the form is valid, Xperience looks for an existing form entry for the submitter (if the visitor is &lt;a href="https://docs.xperience.io/managing-website-content/forms/using-smart-fields-in-forms#Usingsmartfieldsinforms-Contacttrackingrequirement" rel="noopener noreferrer"&gt;identified as an existing Contact&lt;/a&gt;) and updates it, or adds a new Form submission to the database.&lt;/li&gt;
&lt;li&gt;If the Form's Redirect Url is populated, redirect the page to a new URL (usually to display some "thank you" content).&lt;/li&gt;
&lt;li&gt;If Form's Display Text is populated, return Display Text and replace the Form with the Display Text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Redirecting to a new page gives a marketer a lot of control over the visitor's experience 💪🏻, but it also requires maintaining an additional page on the site 😞.&lt;/p&gt;

&lt;p&gt;Using the Display Text is simpler and more convenient 🤗!&lt;/p&gt;

&lt;p&gt;Unfortunately, the Display Text field only accepts plain text. What if we want to respond to a form submission with a helpful message, links, and maybe a Call To Action with an accessible design 🤷🏼‍♂️?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqkivan8kfc51orqo9qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqkivan8kfc51orqo9qg.png" alt="Form metadata in Kentico Xperience administration interface"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems as though we'll need to customize what kind of content we can author here and how it's displayed on the site after a submission 🤓!&lt;/p&gt;

&lt;h2&gt;
  
  
  🗃 How to Customize the Form Management UI
&lt;/h2&gt;

&lt;p&gt;The first thing we need to look for is a way to change the Display Text field's Text input to a Rich Text input.&lt;/p&gt;

&lt;p&gt;This UI element can be found at &lt;code&gt;CMS\CMSModules\BizForms\Tools\BizForm_Edit_General.aspx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t4vphca6nnmgrajitbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t4vphca6nnmgrajitbf.png" alt="Visual Studio Solution Explorer showing path to Form metadata UI control file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key customization we need to make to this file is to register a new "Rich Text" User Control (called &lt;code&gt;HtmlAreaControl&lt;/code&gt;) at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%@&lt;/span&gt; &lt;span class="na"&gt;Register&lt;/span&gt; 
    &lt;span class="na"&gt;Src=&lt;/span&gt;&lt;span class="s"&gt;"~/CMSFormControls/Basic/HtmlAreaControl.ascx"&lt;/span&gt;
    &lt;span class="na"&gt;TagName=&lt;/span&gt;&lt;span class="s"&gt;"HtmlArea"&lt;/span&gt; 
    &lt;span class="na"&gt;TagPrefix=&lt;/span&gt;&lt;span class="s"&gt;"cms"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxylfv4fk9ip0zwq4xdlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxylfv4fk9ip0zwq4xdlp.png" alt="ASPX markup for Form metadata UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that registration added, we can refer to the control further down the file, replacing the &lt;code&gt;&amp;lt;cms:LocalizableTextBox /&amp;gt;&lt;/code&gt; currently being used for Display Text input with a &lt;code&gt;&amp;lt;cms:HtmlAreaControl /&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cms:CMSRadioButton&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"selector-subitem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;cms:LocalizableTextBox&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"txtDisplay"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt; &lt;span class="na"&gt;MaxLength=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is replaced by:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cms:CMSRadioButton&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"selector-subitem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;cms:HtmlArea&lt;/span&gt; &lt;span class="na"&gt;ID=&lt;/span&gt;&lt;span class="s"&gt;"txtDisplay"&lt;/span&gt; &lt;span class="na"&gt;runat=&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt; &lt;span class="na"&gt;MaxLength=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzngwgnl0hqfg00hkg2x6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzngwgnl0hqfg00hkg2x6.png" alt="Change to ASPX markup with HtmlArea control"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these two changes, we can rebuild the CMS application and go back to the Form "General" tab and add Rich Text as our Display Text 🦾!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqj2ye2ewy2bgg4hkphm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqj2ye2ewy2bgg4hkphm.png" alt="Rich Text editor replacing a text field for Form Display Text value"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  📅 How to Customize Razor Class Libraries
&lt;/h2&gt;

&lt;p&gt;Even though we're now storing Rich Text as our Form's Display Text, we won't get very far if we don't change how that Display Text is rendered on the live site 😏.&lt;/p&gt;

&lt;p&gt;Fortunately, ASP.NET Core includes a feature that allows us to &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-6.0&amp;amp;tabs=visual-studio#override-views-partial-views-and-pages" rel="noopener noreferrer"&gt;override a View included in our project from a Razor Class Library&lt;/a&gt; by adding a View file with the exact same name and path as the file from the Razor Class Library 🧐.&lt;/p&gt;

&lt;p&gt;The View that controls how the Form Display Text is rendered after a successful submission is in the Razor Class Library at the following path:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Views/Shared/Kentico/Widgets/FormWidget/_FormWidgetTextConfirmation.cshtml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We don't have a file at that path in our project, so let's create a new empty one 🙃.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure you use the same path and filename, otherwise ASP.NET Core will see your View as a new one and not a replacement for the View coming from the library!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Razor in the original View from the library looks this like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@model string

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"formwidget-submit-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@Model&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Which we are going to replace with the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@model string

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"formwidget-submit-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@Html.Raw(Model)&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwnv00jhkixmo2lkhrgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwnv00jhkixmo2lkhrgg.png" alt="Authoring new Razor View file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By rendering the &lt;code&gt;Model&lt;/code&gt; with &lt;code&gt;Html.Raw()&lt;/code&gt; we ensure our Rich Text Display Text won't be escaped when displayed on the page.&lt;/p&gt;

&lt;p&gt;At this point we can rebuild our ASP.NET Core application and we're all set 🙌🏿!&lt;/p&gt;
&lt;h2&gt;
  
  
  🏁 Conclusion
&lt;/h2&gt;

&lt;p&gt;With our 2 simple customizations, we can try out the Contact form to see our Rich Text message displayed on the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyam3hns0ujze31wmm8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyam3hns0ujze31wmm8c.png" alt="Thank you message with a link displayed on a website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It worked 🎉!&lt;/p&gt;

&lt;p&gt;We can use any content or design to our Display Text that can be authored in Rich Text and craft a "richer" experience (pun intended 😆) without having to go through the process of creating a completely separate page to redirect to.&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/managing-website-content/forms/composing-forms" rel="noopener noreferrer"&gt;Form Builder&lt;/a&gt; - Xperience Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/managing-website-content/forms/composing-forms#Composingforms-Addingfieldvalidation" rel="noopener noreferrer"&gt;Form Builder field validation&lt;/a&gt; - Xperience Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/managing-website-content/forms/using-smart-fields-in-forms#Usingsmartfieldsinforms-Contacttrackingrequirement" rel="noopener noreferrer"&gt;Form Builder Smart Fields&lt;/a&gt; - Xperience Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-6.0&amp;amp;tabs=visual-studio#override-views-partial-views-and-pages" rel="noopener noreferrer"&gt;Overriding Razor Class Library Views&lt;/a&gt; - ASP.NET Core Docs&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md" rel="noopener noreferrer"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>csharp</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Handling Failures - The Result Monad</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 29 Aug 2022 13:32:35 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-the-result-monad-1j25</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-the-result-monad-1j25</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa"&gt;previous post&lt;/a&gt; we looked at the benefits of returning failures instead of throwing exceptions.&lt;/p&gt;

&lt;p&gt;We came up with a &lt;code&gt;Result&lt;/code&gt; type that could represent an failure or a success of a given operation, either containing a &lt;code&gt;string&lt;/code&gt; error message or the value of our operation.&lt;/p&gt;

&lt;p&gt;This custom &lt;code&gt;Result&lt;/code&gt; type met most of our requirements, but to really unlock its power 🦾 we need to turn it into the &lt;code&gt;Result&lt;/code&gt; &lt;a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;monad&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is part 3 of a 3 part series on handling failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n"&gt;Part 1&lt;/a&gt; and &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa"&gt;Part 2&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Refresher on Monads&lt;/li&gt;
&lt;li&gt;Modeling Data Access&lt;/li&gt;
&lt;li&gt;Handling Results&lt;/li&gt;
&lt;li&gt;Map and Bind&lt;/li&gt;
&lt;li&gt;Handling Failures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Refresher on Monads
&lt;/h2&gt;

&lt;p&gt;If you are unsure 😕 of what a monad is, I give a description with some analogies in my previous post &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-modeling-missing-data-the-maybe-monad-2c7i#what-is-a-monad"&gt;Kentico Xperience Design Patterns: Modeling Missing Data - The Maybe Monad&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, if you don't feel like navigating away and want a quick explanation, think of a monad as a container 🥫, in the same way that &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; are containers in C# (they are also monads).&lt;/p&gt;

&lt;p&gt;You can manipulate these containers in a standard way, independent of what's inside and each type of monad represents a unique concept 🧠. A &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; represents a value &lt;code&gt;T&lt;/code&gt; that will be available at some point in the future and &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; represents a collection of 0 or more of some type &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's worth noting that monads can contain other monads, because they don't care what kind of data they contain (ex: &lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;List&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's consider the &lt;code&gt;Result&amp;lt;TValue, TError&amp;gt;&lt;/code&gt; monad 😮.&lt;/p&gt;

&lt;p&gt;It is a container that represents an operation that either succeeded or failed (past tense). If it succeeded then it will have a value of type &lt;code&gt;TValue&lt;/code&gt; and if it failed it will have an error of type &lt;code&gt;TError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The specific implementation of the &lt;code&gt;Result&lt;/code&gt; monad that we will be using comes from the C# library &lt;a href="https://github.com/vkhorikov/CSharpFunctionalExtensions"&gt;CSharpFunctionalExtensions&lt;/a&gt; 🎉 and we will use the simplified &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; where the &lt;code&gt;TError&lt;/code&gt; error type is a &lt;code&gt;string&lt;/code&gt; and doesn't need to be specified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling Data Access
&lt;/h2&gt;

&lt;p&gt;Now we can answer the question of when and where do we actually use &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recommend starting somewhere our application is performing an 'operation' which could succeed or fail and we're currently modeling the failure case with a &lt;code&gt;throw new Exception(...);&lt;/code&gt; or by ignoring it altogether.&lt;/p&gt;

&lt;p&gt;Here's an example that might look similar to some Kentico Xperience code we have written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageDataContextRetriever&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDefaultBackgroundImage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;bgImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;heroImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the applications I work on, it's pretty common to grab bits of data from various pages, custom module classes, the media library, ect... to gather all the content needed to render a page 🤓.&lt;/p&gt;

&lt;p&gt;However, each of these operations needs to go to the database, an external web service, or the file system to look for something. It might even have some business rules 📃 around how the data is retrieved. &lt;/p&gt;

&lt;p&gt;If something goes wrong in the sample code above we can assume that an exception is going to thrown and &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n"&gt;centralized exception handling&lt;/a&gt; will catch it and display an error page.&lt;/p&gt;

&lt;p&gt;We end up skipping the rest of the operations, even if they would have succeeded 😞. With centralized exception handling we typically never partially display a page that experienced some failures.&lt;/p&gt;

&lt;p&gt;Let's update the code to use &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; and see where that gets us:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageDataContextRetriever&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDefaultBackgroundImage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// 🤔&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Our potential failures are no longer hidden as exceptions behind a method signature. Instead, we're requiring consumers of the &lt;code&gt;authorService&lt;/code&gt; and &lt;code&gt;blogService&lt;/code&gt; to deal with both the success and failure cases.&lt;/p&gt;

&lt;p&gt;However, we now have a bunch of &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; instances that are useless to our &lt;code&gt;BlogPostViewModel&lt;/code&gt; 😩, so we'll need to do something to get the data they potentially contain to our Razor views.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handling Results
&lt;/h2&gt;

&lt;p&gt;One option for rendering when using &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; is to update the &lt;code&gt;BlogPostViewModel&lt;/code&gt; to use &lt;code&gt;Result&lt;/code&gt; properties and 'unwrap' them in the View:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HeroImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BgImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this case, we'd pass our results directly to the &lt;code&gt;BlogPostViewModel&lt;/code&gt; constructor in our &lt;code&gt;Index()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, in our View we can unwrap conditionally to access the values or errors that were the outcomes of each operation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@model Sandbox.BlogPostViewModel

@if (Model.HeroImage.TryGetValue(out var img))
{
  &lt;span class="c"&gt;&amp;lt;!-- We retrieved content successfully --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"img.Path"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"img.AltText"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
else if (Model.HeroImage.TryGetError(out string err))
{
  &lt;span class="c"&gt;&amp;lt;!-- Content retrieval failed - show error to admins --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;page-builder-mode&lt;/span&gt; &lt;span class="na"&gt;exclude=&lt;/span&gt;&lt;span class="s"&gt;"Live"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Could not retrieve hero image:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;@err&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/page-builder-mode&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is an interesting approach because it allows us to gracefully 💃🏽 display error information for any operations that failed, while continuing to show the correct content for those that succeed.&lt;/p&gt;

&lt;p&gt;In this example we were able to treat all the &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; as independent because &lt;em&gt;none of the operations were conditional on the others&lt;/em&gt;, but that's not always the case 😮.&lt;/p&gt;

&lt;p&gt;Let's enhance our &lt;code&gt;BlogPostViewModel&lt;/code&gt; to include related posts (by author) and those post's taxonomies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;relatedPosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Taxonomy&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relatedPosts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We're now an in awkward situation where we have to pass &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; to our services. Those service methods will have to do some checks to see if the results succeeded or failed and conditionally perform the operations.&lt;/p&gt;

&lt;p&gt;The monad is 'infecting' 😷 our code, and not in a good way.&lt;/p&gt;

&lt;p&gt;Ideally we'd only call our services if the depending operations (getting the &lt;code&gt;Author&lt;/code&gt; and related &lt;code&gt;BlogPost&lt;/code&gt;s) succeeded. As with most monads, we'll have a better experience by "lifting" our code up into the &lt;code&gt;Result&lt;/code&gt; instead of bringing the &lt;code&gt;Result&lt;/code&gt; down into our code 😉.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is similar to how we work with &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt;. We don't often pass values of this type as arguments to methods. Instead we &lt;code&gt;await&lt;/code&gt; them to get their values and pass those to our methods.&lt;/p&gt;

&lt;p&gt;We can also compare this to creating methods that operate on a single value of type &lt;code&gt;T&lt;/code&gt; vs updating all of them to accept &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fortunately there's &lt;a href="https://youtu.be/EhkxDIr0y2U"&gt;an API for that&lt;/a&gt;. We want to use &lt;code&gt;Result&amp;lt;T&amp;gt;.Bind()&lt;/code&gt; and &lt;code&gt;Result&amp;lt;T&amp;gt;.Map()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// result is Result&amp;lt;(Author, List&amp;lt;BlogPost&amp;gt;, List&amp;lt;Taxonomy&amp;gt;)&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
           &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
                   &lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                   &lt;span class="p"&gt;});&lt;/span&gt;
           &lt;span class="p"&gt;})&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
           &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Taxonomy&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
                   &lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                   &lt;span class="p"&gt;});&lt;/span&gt;
           &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So, what's going on here 😵😵?&lt;/p&gt;

&lt;p&gt;Let's break it down piece by piece 🤗.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;authorService.GetAuthor()&lt;/code&gt;, &lt;code&gt;blogService.GetRelatedPostsByAuthor()&lt;/code&gt; and &lt;code&gt;taxonomyService.GetForPages()&lt;/code&gt; return &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;, so we can chain off them with &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;'s extension methods.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Map()&lt;/code&gt; and &lt;code&gt;Bind()&lt;/code&gt; extension methods will only execute their delegate parameter &lt;em&gt;if&lt;/em&gt; the &lt;code&gt;Result&lt;/code&gt; is in a successful state - that is, if the previous operation didn't fail 👍🏾.&lt;/p&gt;

&lt;p&gt;This means we only get related blog posts if we were able to get the current post's author. And, we only get the taxonomies if we were able to get related blog posts.&lt;/p&gt;

&lt;p&gt;If any part of this dependency chain fails, all later operations are skipped and the failed &lt;code&gt;Result&lt;/code&gt; is returned from the final extension method 🙌🏼.&lt;/p&gt;
&lt;h2&gt;
  
  
  Map and Bind
&lt;/h2&gt;

&lt;p&gt;To help make the above code a bit more transparent, let's review the functionality of &lt;code&gt;Map()&lt;/code&gt; and &lt;code&gt;Bind()&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Map()&lt;/code&gt; is like LINQ's &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations#select"&gt;Select&lt;/a&gt; method, which converts the contents of the &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; from one value to another (it could be to the same or different types).&lt;/p&gt;

&lt;p&gt;We often use &lt;code&gt;Map()&lt;/code&gt; when we want to transform the data returned from a method call to something else - like converting a &lt;a href="https://en.wikipedia.org/wiki/Data_transfer_object"&gt;DTO&lt;/a&gt; to a view model. We don't know if the method successfully completed its operation 😏, we assume it did and &lt;em&gt;declare&lt;/em&gt; how to transform the result. Our transformation is skipped if the data retrieval failed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bind()&lt;/code&gt; is like LINQ's &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations#selectmany"&gt;SelectMany&lt;/a&gt;, which flattens out a nested &lt;code&gt;Result&lt;/code&gt; (ex &lt;code&gt;IEnumerable&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;Result&amp;lt;Result&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We'll typically use &lt;code&gt;Bind()&lt;/code&gt; when we have dependent operations.&lt;/p&gt;

&lt;p&gt;When one service returns a &lt;code&gt;Result&lt;/code&gt; and we only want to call another service if the first one succeeded we will write something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OtherData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service1&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;service2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOtherData&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You'll notice 🧐 we have a common pattern of &lt;code&gt;Bind()&lt;/code&gt; followed by a nested &lt;code&gt;Map()&lt;/code&gt;. &lt;code&gt;Bind()&lt;/code&gt; is calling the dependent operation and &lt;code&gt;Map()&lt;/code&gt; lets us continue to gather up the data from each operation into a new C# &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples"&gt;tuple&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can skip the braces, type annotations, and &lt;code&gt;return&lt;/code&gt; keywords and use expressions to keep our code terse and declarative - reading like a recipe 👩🏻‍🍳:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// result is Result&amp;lt;(Author, List&amp;lt;BlogPost&amp;gt;, List&amp;lt;Taxonomy&amp;gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taxonomies&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If this feels too unfamiliar, and we want a place to add breakpoints for debuggability, we can extract each delegate to a method and pass the &lt;a href="https://stackoverflow.com/questions/3841990/are-there-any-benefits-to-using-a-c-sharp-method-group-if-available"&gt;method group&lt;/a&gt;, which can be even more readable 😁:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bgImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heroImage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// Here is our recipe of operations&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetRelatedPosts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetTaxonomies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetRelatedPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Taxonomy&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetTaxonomies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taxonomies&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Handling Failures
&lt;/h2&gt;

&lt;p&gt;If our entire pipeline of operations are dependent and we should skip trying to render content if we can't access everything we need, we can use the &lt;code&gt;Match()&lt;/code&gt; extension and provide delegates that define what should happen for both success and failure scenarios:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taxonomies&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="n"&gt;viewModel&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
               &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we're really seeing the power of composing &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; 💪🏽. Each of our services &lt;em&gt;can&lt;/em&gt; fail, but that doesn't complicate our logic that composes the data returned by each service.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; we could use exceptions to signal errors - hidden from method signatures and used as secret control flow 😝. &lt;/p&gt;

&lt;p&gt;Or, we have a bunch of conditional statements 😝, checking to see what 'state' we are in (success/failure), often modeled with &lt;a href="https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/"&gt;Nullable Reference Types&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; we let the monad maintain the internal success/failure state and write our code as a series of steps that clearly defines what we need to proceed.&lt;/p&gt;

&lt;p&gt;If we have operations that aren't dependent, we can gather up all the various &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; values, put them in our view model and handle the conditional rendering in the view 🤘🏼.&lt;/p&gt;

&lt;p&gt;My general recommendation is to separate independent sets of operations into View Components 🤔. We treat each View Component as a boundary for errors or failures instead of populating a view model with a bunch of &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt; values, potentially making our views overly complex.&lt;/p&gt;

&lt;p&gt;We can create a &lt;code&gt;ViewComponent&lt;/code&gt; extension method to help us easily convert a failed &lt;code&gt;Result&lt;/code&gt; to an error View:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewComponentResultExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IViewComponentResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;ViewComponent&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ComponentFailure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can place the failure View in a shared location &lt;code&gt;~/Views/Shared/ComponentFailure.cshtml&lt;/code&gt; and have it show an error message in the Page Builder and Preview modes, but hide everything on the Live site 🙂:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@model string

&lt;span class="nt"&gt;&amp;lt;page-builder-mode&lt;/span&gt; &lt;span class="na"&gt;exclude=&lt;/span&gt;&lt;span class="s"&gt;"Live"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;There was a problem loading this component.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;@Model&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/page-builder-mode&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If we're following the &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-mvc-is-dead-long-live-ptvc-4635"&gt;PTVC pattern&lt;/a&gt;, we can use this in a View Component as follows:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogViewComponent&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ViewComponent&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IViewComponentResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;BlogPost&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;authorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;blogService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRelatedPostsByAuthor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;taxonomyService&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetForPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taxonomies&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlogPostViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomies&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We now have the added benefit of an &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#methods"&gt;expression bodied member&lt;/a&gt; as a method implementation 😲!&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Moving from C# exceptions to the &lt;code&gt;Result&lt;/code&gt; monad can take some getting used to. It's also a change that should be discussed with out team members and implemented where appropriate (Exceptions still have their place! 🧐).&lt;/p&gt;

&lt;p&gt;If we do decide it's an option worth exploring, what do we gain?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Honest methods that don't hide the possibility of failures from their signatures.&lt;/li&gt;
&lt;li&gt;A consistent set of patterns and tools for combining &lt;code&gt;Result&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;Local, targeted handling of failures that prevents them from failing an entire page (unlike centralized exception handling).&lt;/li&gt;
&lt;li&gt;A recipe of operations that reads like a set of instructions on how to gather up the data we need to proceed through our app.&lt;/li&gt;
&lt;li&gt;No repeated boilerplate if/else statements to handle various failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you think your Kentico Xperience site might benefit from returning failures and using the &lt;code&gt;Result&lt;/code&gt; monad, checkout the &lt;a href="https://github.com/vkhorikov/CSharpFunctionalExtensions"&gt;CSharpFunctionalExtensions&lt;/a&gt; library and my library-in-progress &lt;a href="https://github.com/wiredviews/xperience-community-cqrs"&gt;XperienceCommunity.CQRS&lt;/a&gt;. It codifies these patterns for data retrieval and integrates cross-cutting concerns like logging and caching.&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts 👋!&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa"&gt;Kentico Xperience Design Patterns: Handling Failures - Return Errors, Don't Throw Them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;Monad&lt;/a&gt; - Wikipedia&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-modeling-missing-data-the-maybe-monad-2c7i#what-is-a-monad"&gt;Kentico Xperience Design Patterns: Modeling Missing Data - The Maybe Monad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vkhorikov/CSharpFunctionalExtensions"&gt;CSharpFunctionalExtensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n"&gt;Kentico Xperience Design Patterns: Handling Failures - Centralized Exception Handling &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations#select"&gt;LINQ Select&lt;/a&gt; - Microsoft Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations#selectmany"&gt;LINQ SelectMany&lt;/a&gt; - Microsoft Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples"&gt;C# Tuple&lt;/a&gt; - Microsoft Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.com/questions/3841990/are-there-any-benefits-to-using-a-c-sharp-method-group-if-available"&gt;C# Method Groups&lt;/a&gt; - Stack Overflow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/"&gt;Nullable Reference Types&lt;/a&gt; - Microsoft Docs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-mvc-is-dead-long-live-ptvc-4635"&gt;Kentico Xperience Design Patterns: MVC is Dead, Long Live PTVC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#methods"&gt;C# Expression Bodied Member&lt;/a&gt; - Microsoft Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wiredviews/xperience-community-cqrs"&gt;XperienceCommunity.CQRS&lt;/a&gt; - GitHub&lt;/li&gt;
&lt;/ul&gt;


Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;





&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>aspnetcore</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Registering Xperience Dependencies</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 27 Jun 2022 14:03:29 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-registering-xperience-dependencies-52lm</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-registering-xperience-dependencies-52lm</guid>
      <description>&lt;p&gt;Kentico Xperience has supported dependency resolution (both in ASP.NET Core and .NET 4.x) for a few versions, through the &lt;a href="https://en.wikipedia.org/wiki/Service_locator_pattern"&gt;service locator pattern&lt;/a&gt; 🧐.&lt;/p&gt;

&lt;p&gt;Now, with Kentico Xperience 13, it fully supports &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection"&gt;dependency injection&lt;/a&gt; (DI) out of the box using ASP.NET Core. We can always inject any of Xperience's built-in services into our custom types without needing to worry about how those services are constructed 🤩.&lt;/p&gt;

&lt;p&gt;However, we still need to be conscious of Xperience's underlying architecture when doing anything beyond simple use-cases 😮.&lt;/p&gt;

&lt;p&gt;If we don't pull back the curtain, there are things we might do to improve convenience that have significant impacts on code reusability, discoverability, and automated testing 😟.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How does Xperience manage internal dependencies?&lt;/li&gt;
&lt;li&gt;How can we customize Xperience's internal types?&lt;/li&gt;
&lt;li&gt;When do customizations become inflexible?&lt;/li&gt;
&lt;li&gt;Best practices for registering custom types with Xperience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💎 Xperience's DI Container
&lt;/h2&gt;

&lt;p&gt;In Kentico Xperience's core, there is a static service locator class &lt;code&gt;CMS.Core.Service&lt;/code&gt;. With it, we can request any service that Xperience has registered in its internal DI container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IUserInfoProvider&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;UserInfo&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: This DI container is actually backed by &lt;code&gt;Microsoft.Extensions.DependencyInjection.ServiceCollection&lt;/code&gt;, which is the same type that backs ASP.NET Core's DI container 🤓 and it works in both .NET 4.8 and .NET 6.0.&lt;/p&gt;

&lt;p&gt;However, it is a completely separate container from the one ASP.NET Core provides us 🤔 so we should follow Xperience's documentation for dependency registration for Xperience type customization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can also use this service locator to register types with Xperience's DI container. The service registration needs to be performed in a custom Xperience &lt;code&gt;Module&lt;/code&gt; during the &lt;code&gt;PreInit&lt;/code&gt; phase of application startup:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserInfoProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserInfoProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;ObjectQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UserInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do something special 🤷‍♂️&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CustomModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomModule&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;OnPreInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Since it's using the .NET &lt;code&gt;ServiceCollection&lt;/code&gt; underneath, we can even use it to register configuration via the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0"&gt;options pattern&lt;/a&gt; (again in the &lt;code&gt;Module.OnPreInit&lt;/code&gt; method):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// I'm making up a `CustomUserOptions` type here as an example&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;DefaultUserID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomUserOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultUserID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This means we should also be able to retrieve this configuration through &lt;code&gt;Service.Resolve&lt;/code&gt; anywhere in our application:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomUserOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultUserID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;This isn't very useful outside of an ASP.NET Core application or modern (.netcoreapp3.1 +) .NET class libraries. We should use the built-in .NET DI container for this kind of thing, so it's more just an interesting observation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We could use &lt;code&gt;Service&lt;/code&gt; to get access to any Xperience type we want in our ASP.NET Core app, but in user-land code (code that isn't part of a framework), using a service locator for retrieving services &lt;a href="https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/"&gt;is an anti-pattern&lt;/a&gt; 😩.&lt;/p&gt;

&lt;p&gt;Instead, use constructor dependency injection wherever possible 🙂, and hide away your use of the &lt;code&gt;Service&lt;/code&gt; class if you must use it. This will make your code more testable and predictable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Customizing Xperience's Internal Types
&lt;/h2&gt;

&lt;p&gt;Above, we looked at an example of how to customize Xperience's built-in types (like &lt;code&gt;IUserInfoProvider&lt;/code&gt;) using the &lt;code&gt;Service.Use&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;However, there's a much more common pattern using &lt;code&gt;assembly&lt;/code&gt; attributes for registering dependencies that you'll see in blog posts and &lt;a href="https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example"&gt;Xperience's documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This tells Xperience to add our custom type to its services container&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterCustomProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Sandbox.Data&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserInfoProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserInfoProvider&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;ObjectQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UserInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// do something special 🤷‍♂️&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This assembly attribute registration pattern is also used for &lt;a href="https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code"&gt;custom modules&lt;/a&gt;, for example when creating a custom module to handle &lt;a href="https://docs.xperience.io/custom-development/handling-global-events"&gt;global events&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Sandbox.Data&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomGlobalEventsModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CustomGlobalEventsModule&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;DocumentEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;After&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Document_Insert_After&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Document_Insert_After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DocumentEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Add custom actions here&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These &lt;code&gt;assembly&lt;/code&gt; attributes are a lot simpler and seem to be the way to go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create our custom class&lt;/li&gt;
&lt;li&gt;Add an attribute above &lt;/li&gt;
&lt;li&gt;Our customization is registered and ready to run 😁!&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: We'll typically only use &lt;code&gt;Service.Use&lt;/code&gt; when we want to have an explicit ordering to service decoration, because the &lt;code&gt;assembly&lt;/code&gt; attributes for dependency registration &lt;a href="https://docs.xperience.io/custom-development/decorating-system-services#Decoratingsystemservices-Decorateservicesviadependencyinjection"&gt;are non-deterministic&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Non Flexible Dependency Registration
&lt;/h2&gt;

&lt;p&gt;Xperience's documentation provides &lt;a href="https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example"&gt;plenty&lt;/a&gt; of &lt;a href="https://docs.xperience.io/custom-development/customizing-providers/custom-email-provider-example"&gt;examples&lt;/a&gt; of &lt;a href="https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code"&gt;registering customizations&lt;/a&gt; in the same assembly, and often the same file, where they are defined.&lt;/p&gt;

&lt;p&gt;This is perfect for helping developers understand concepts 👍🏾. However, as our applications grow in size and complexity, and we discover use-cases for using Xperience's APIs &lt;a href="https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally"&gt;outside of traditional web applications&lt;/a&gt;, we will find we've strung ourselves up in our dependencies 😒.&lt;/p&gt;

&lt;p&gt;In ASP.NET Core, we are used to adding NuGet packages containing types that customize our applications. We enable those customizations when we call extension methods that add types to the &lt;code&gt;IServiceCollection&lt;/code&gt; or modify request processing of the &lt;code&gt;IApplicationBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Simply adding a NuGet package or reference to a .NET project (typically) does not change the behavior of our application - this is going to be a .NET developer's expectation 😤.&lt;/p&gt;

&lt;p&gt;The problem with assembly registration attributes like &lt;code&gt;[assembly: RegisterModule()]&lt;/code&gt; and &lt;code&gt;[assembly: RegisterCustomProvider()]&lt;/code&gt; is they are DI container configuration calls that &lt;strong&gt;always&lt;/strong&gt; 😲 execute in a running application, even if they aren't in our main application's code.&lt;/p&gt;
&lt;h3&gt;
  
  
  External Applications
&lt;/h3&gt;

&lt;p&gt;Imagine we create a &lt;code&gt;.netstandard2.0&lt;/code&gt; class library (so that we can share its code with both our CMS .NET 4.8 application and live site ASP.NET Core application) and this library contains our custom Page Type definitions and some other code we want to share between both applications.&lt;/p&gt;

&lt;p&gt;It's also likely that this library registers several custom providers and modules using &lt;code&gt;assembly&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;Now, if we decide we want to use this library in &lt;a href="https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally"&gt;an external application&lt;/a&gt; like &lt;a href="https://github.com/wiredviews/xperience-appsettings-json-registration"&gt;a console application&lt;/a&gt;, we cannot opt-out of those registered modules and custom providers 😔. They always come along for the ride and automatically augment our system.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.wiredviews.com/services/web-design-and-development#section903d04b3-6baa-4a65-ba32-59acc66f196d"&gt;WiredViews&lt;/a&gt; we often use .NET 6 console applications to iterate quickly on ETL/data import or migration processes and often we want to selectively opt-in to specific custom modules or types and opt-out of others.&lt;/p&gt;

&lt;p&gt;When the referenced code contains these &lt;code&gt;assembly&lt;/code&gt; attributes, our hands are tied - we can't turn them off.&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing and Troubleshooting
&lt;/h3&gt;

&lt;p&gt;Other times, a developer might want to test a specific scenario locally with a custom provider that behaves slightly differently or use an updated custom module implementation.&lt;/p&gt;

&lt;p&gt;If class libraries are distributed through NuGet packages and those libraries have &lt;code&gt;assembly&lt;/code&gt; registration attributes, what are the answers to the following questions 🤔?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How would a developer easily set up their testing scenario?&lt;/li&gt;
&lt;li&gt;How can they control what dependencies are registered and executed?&lt;/li&gt;
&lt;li&gt;How do new developers even know what customizations are running in an application? Do they have to go digging through all the source code to find out 😩?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the modern ASP.NET Core world, we're used to commenting out an extension method in our middleware pipeline or services registration to turn functionality off.&lt;/p&gt;

&lt;p&gt;We could use app settings to replicate this behavior even when our NuGet packages contain &lt;code&gt;[assembly: RegisterModule()]&lt;/code&gt; calls. Those modules or custom types would need to check the app settings to enable/disable the custom behavior 😯.&lt;/p&gt;

&lt;p&gt;But, this requires developers to have one set of expectations for non-Xperience application customizations and a completely different set for Xperience customizations 😑.&lt;/p&gt;

&lt;p&gt;There's a better way!&lt;/p&gt;
&lt;h2&gt;
  
  
  Flexible Dependency Registration
&lt;/h2&gt;

&lt;p&gt;All we need to do to bring some consistency and flexibility to our applications is to remove our dependency registration from our class libraries and move it into our consuming applications.&lt;/p&gt;

&lt;p&gt;This way customizations only turn 'on' when they are explicitly registered in the applications that want them 🤗.&lt;/p&gt;

&lt;p&gt;In the ASP.NET Core world, if we are &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-good-startup-cs-hygiene-3klm"&gt;practicing good Startup.cs hygeine&lt;/a&gt; we might end up with a &lt;code&gt;XperienceRegistrations.cs&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterCustomProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Sandbox.Web.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;XperienceRegistrations&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="nf"&gt;AddAppXperience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// register our Xperience services with ASP.NET Core&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddKentico&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice, we opt-in to our Xperience customizations that require &lt;code&gt;assembly&lt;/code&gt; attributes by registering them in the same locationn that all our other Xperience-specific customizations are defined.&lt;/p&gt;

&lt;p&gt;This makes it extremely clear what is being customized in an app and allows developers to turn things on/off in a way that is very similar to all the other code they work with in ASP.NET Core 💪🏽.&lt;/p&gt;

&lt;p&gt;We can follow this pattern in our CMS ASP.NET 4.8 application by creating a &lt;code&gt;DependencyRegistrationModule&lt;/code&gt; in that application's project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DependencyRegistrationModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterCustomProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CMSCustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CMSApp.Configuration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DependencyRegistrationModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DependencyRegistrationModule&lt;/span&gt; 
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DependencyRegistrationModule&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;OnPreInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// register any services we want to access through&lt;/span&gt;
            &lt;span class="c1"&gt;// Service.Resolve in custom modules or elsewhere&lt;/span&gt;
            &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreditCardService&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this example, we intentionally registered &lt;code&gt;CMSCustomGlobalEventsModule&lt;/code&gt; in the CMS but not in our ASP.NET Core application. Had we defined these &lt;code&gt;assembly&lt;/code&gt; registration attributes in a shared class library, we wouldn't be able to do this 😏.&lt;/p&gt;
&lt;h3&gt;
  
  
  Managing Registration Complexity
&lt;/h3&gt;

&lt;p&gt;We might look at this and think we traded one form of complexity for another. Sure, we now have full control over which services and modules are registered in each application, but we also have to remember to register them in all applications that need them 🤨. Before, all we needed to do was reference the class library and ... bam, it was done for us 🤷🏻‍♀️.&lt;/p&gt;

&lt;p&gt;This is true, however we can recreate another pattern that ASP.NET Core libraries often use to make things easier for ourselves - gathering all dependencies up into a single place to be registered.&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;assembly&lt;/code&gt; attributes are really just calls into Xperience's internals to 'register' things, we can reproduce this with a single Custom Module that makes these calls explicitly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Sandbox.Library&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LibraryRegistrationModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;LibraryRegistrationModule&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LibraryRegistrationModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;OnPreInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomUserInfoProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CustomGlobalEventsModule&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we can just include 1 &lt;code&gt;assembly&lt;/code&gt; registration attribute to register everything in our library:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LibraryRegistrationModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is like an ASP.NET Core library's &lt;code&gt;UseX()&lt;/code&gt; extension method on &lt;code&gt;IServiceCollection&lt;/code&gt;. If we are ok with the defaults, we call that extension and the library handles everything for us, but we still get to explicitly opt-in.&lt;/p&gt;

&lt;p&gt;Likewise, if we are ok with our class library's Xperience customization defaults, adding a single &lt;code&gt;assembly&lt;/code&gt; registration attribute brings in everything 👏🏿.&lt;/p&gt;

&lt;p&gt;They key here is that adding a reference to an assembly (either via a .NET project or NuGet reference) doesn't change the behavior of our system. We have to be clear that we want a customization and add a line of code 😎. This means we can just as easily opt-out without having to throw away the entire class library's code.&lt;/p&gt;
&lt;h2&gt;
  
  
  🏁 Conclusion
&lt;/h2&gt;

&lt;p&gt;Kentico Xperience 13 has been able to support both the older ASP.NET Web Form CMS application and modern ASP.NET Core applications with shared libraries and APIs. This is an impressive feat of engineering.&lt;/p&gt;

&lt;p&gt;Until we are in a fully ASP.NET Core world with Kentico Xperience (hopefully, just a few weeks away from the June 2022 publication of this blog post), we will need to juggle platform dependencies and customizations in a couple different ways.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are interested in the future of Kentico Xperience from a technical perspective, checkout this video below from the &lt;a href="https://connection.xperience.io/"&gt;Kentico Xperience Connection: 2022 Conference&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Xperience &lt;code&gt;assembly&lt;/code&gt; attributes for dependency registration let us tap into the platform for full customization.&lt;/p&gt;

&lt;p&gt;However, if we aren't careful, referencing a class library or NuGet package forces consumers to use our customizations because with these &lt;code&gt;assembly&lt;/code&gt; attributes the dependencies are automatically 'on'. This makes our code less reusable and our applications less flexible.&lt;/p&gt;

&lt;p&gt;Thinking about dependencies in a modern way and following the patterns of ASP.NET Core gives us some better approaches that make our applications more understandable and debuggable, and let's us opt-in to the Xperience customizations we want.&lt;/p&gt;

&lt;p&gt;We should be explicit about which types and modules our applications use by registering them next our our applications other dependency injection management.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oi3c46K7SUs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wikipedia: &lt;a href="https://en.wikipedia.org/wiki/Service_locator_pattern"&gt;Service Locator Pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia: &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection"&gt;Dependency Injection&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ASP.NET Core Docs: &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0"&gt;Options Pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Mark Seemann Blog: &lt;a href="https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/"&gt;Service Locator Anti-Pattern&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example"&gt;Custom Info Provider Example&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code"&gt;Custom Module Initialization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/custom-development/handling-global-events"&gt;Global Events&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/custom-development/decorating-system-services#Decoratingsystemservices-Decorateservicesviadependencyinjection"&gt;Service Decoration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/custom-development/customizing-providers/custom-email-provider-example"&gt;Custom Email Provider Example&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Xperience Docs: &lt;a href="https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally"&gt;Using Xperience APIs Externally&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Xperience Community: &lt;a href="https://github.com/wiredviews/xperience-appsettings-json-registration"&gt;App Settings JSON Registration Library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WiredViews: &lt;a href="https://www.wiredviews.com/services/web-design-and-development#section903d04b3-6baa-4a65-ba32-59acc66f196d"&gt;Web Design and Development Services&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kentico Xperience Design Patterns: &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-good-startup-cs-hygiene-3klm"&gt;Good Startup.cs Hygiene&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;Also check out the &lt;a href="https://xperience.io/services/training/developer-hub"&gt;Kentico Xperience Developer Hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Protecting Hard Coded Identifiers</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 13 Jun 2022 15:32:23 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-protecting-hard-coded-identifiers-4f88</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-protecting-hard-coded-identifiers-4f88</guid>
      <description>&lt;p&gt;When we need access to specific values or data in our applications, we might inject them through configuration (especially if they are different for each environment), or store them in a database with an identifier and access them by this identifier in our code.&lt;/p&gt;

&lt;p&gt;The first approach is often thought of as being architecturally sound 😎 because the application doesn't need to re-built (or even re-deployed) if the data changes. We only need to update the configuration, and maybe restart the app.&lt;/p&gt;

&lt;p&gt;The second is considered bad 😒, because an accidental or incorrect change to the database means an application needs rebuilt and redeployed.&lt;/p&gt;

&lt;p&gt;But, what if hard coding an identifier wasn't so risky and the identifier wasn't environment specific 😮? That sounds great, but we'll need a way of guaranteeing these things!&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When would we hard code identifiers in Xperience?&lt;/li&gt;
&lt;li&gt;How do we ensure they are stable between environments?&lt;/li&gt;
&lt;li&gt;How do we protect them from problematic modifications?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💎 Hard Coding Identifiers
&lt;/h2&gt;

&lt;p&gt;Typically, when using a &lt;a href="https://xperience.io/simplicity-through-a-unified-dxp"&gt;DXP&lt;/a&gt;, we will let content managers and marketers customize content for a site 😉, but sometimes it's more convenient to code content directly into an HTML template.&lt;/p&gt;

&lt;p&gt;An example of this could be product pages on a site with thousands of products, each with a link to the Contact Us page (&lt;code&gt;/contact-us&lt;/code&gt;) at the bottom of the layout. This scenario isn't appropriate for the &lt;a href="https://docs.xperience.io/developing-websites/page-builder-development"&gt;Page Builder&lt;/a&gt; and there's not much benefit to adding a custom Page Type field to a page in the content tree for the "Contact Us" link.&lt;/p&gt;

&lt;p&gt;What's the best way to add these links to our templates 🤷‍♀️?&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating Page Links
&lt;/h3&gt;

&lt;p&gt;Hard-coding links in templates is a scenario where we want to reference an identifier in code, because we don't want to maintain a hard-coded URL over time.&lt;/p&gt;

&lt;p&gt;Yes, Xperience's &lt;a href="https://docs.xperience.io/managing-website-content/working-with-pages/managing-page-urls#ManagingpageURLs-FormerURLs"&gt;former URLs feature&lt;/a&gt; helps to ensure that moving pages around in the Content Tree or changing a URL slug will never break a URL 😅. However, there's several benefits to not hard-coding URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO: crawlers won't need to follow 301 redirects of moved pages&lt;/li&gt;
&lt;li&gt;UX: visitors won't need to follow 301 redirects and will have faster page loads&lt;/li&gt;
&lt;li&gt;Developers: it will be easier to maintain a site when links aren't hard-coded

&lt;ul&gt;
&lt;li&gt;An innocent looking &lt;code&gt;/special-product&lt;/code&gt; link could redirect to something completely unexpected 😑!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Content Management: Multi-lingual sites will have a different URL per culture, which means a hard-coded path won't work 😞.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok, we've decided we do want to sometimes hard-code links, but we don't want to hard-code URLs. So, instead, we'll use an identifier of the page we are linking to.&lt;/p&gt;

&lt;p&gt;In Xperience we have a couple of options to pick from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NodeID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DocumentID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DocumentGUID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NodeGUID&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which should we choose 🧐?&lt;/p&gt;

&lt;h2&gt;
  
  
  🏛 Keeping Identifiers Stable Between Environments
&lt;/h2&gt;

&lt;p&gt;If we want our URLs to be correctly generated on a multi-lingual site, we'll want to use one of the Node values, since the Document ones reference a single specific culture for a page - it wouldn't be a very friendly site if we always generated URLs to the Spanish site for all the other cultures 😁.&lt;/p&gt;

&lt;p&gt;Xperience's SQL Server database will auto-increment page integer IDs, which means creating a page in one environment could associate it with a &lt;code&gt;NodeID&lt;/code&gt; that is being used by another page in a different environment.&lt;/p&gt;

&lt;p&gt;If we have "Local", "Test", and "Production" environments, we don't want our code to fail to generate URLs 😬 (or generate incorrect URLs) because the identifier we reference isn't correct for that environment.&lt;/p&gt;

&lt;p&gt;When using &lt;a href="https://docs.xperience.io/deploying-websites/content-staging"&gt;content staging&lt;/a&gt; Xperience guarantees pages synced from one environment to another will keep their same &lt;code&gt;GUID&lt;/code&gt; values (&lt;code&gt;NodeGUID&lt;/code&gt; and &lt;code&gt;DocumentGUID&lt;/code&gt;) while their &lt;code&gt;ID&lt;/code&gt; values can change.&lt;/p&gt;

&lt;p&gt;So, given the option of &lt;code&gt;NodeID&lt;/code&gt; and &lt;code&gt;NodeGUID&lt;/code&gt;, we are going to select &lt;code&gt;NodeGUID&lt;/code&gt; because it's stable between environments 🙌🏾!&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗 Generating URLs
&lt;/h2&gt;

&lt;p&gt;Now that we've decided on our stable page identifier, how are we going to generate URLs for those pages?&lt;/p&gt;

&lt;p&gt;We could use Page Type &lt;code&gt;Guid&lt;/code&gt; fields combined with the Page Selector Form Control, and then retrieve the URL in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;nodeGUID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CTALinkPageNodeGUID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;linkedPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageRetrieve&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeGUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nodeGUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"CTA|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nodeGUID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkedPage&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;PageUrl&lt;/span&gt; &lt;span class="n"&gt;linkedPageUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageUrlRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkedPage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ProductViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linkedPageUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This isn't too complicated, but the whole idea here was to generate the URLs without content management customization, so a Page Type field doesn't align with our goal 🙁.&lt;/p&gt;
&lt;h3&gt;
  
  
  Page Link Tag Helper
&lt;/h3&gt;

&lt;p&gt;Instead, I'm recommending we use an &lt;a href="https://docs.microsoft.com/en-US/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-6.0"&gt;ASP.NET Core Tag Helper&lt;/a&gt; that will enhance &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; elements in our Views:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;xp-page-link=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This Tag Helper will use one of our identifiers to perform a very similar series of data retrieval steps to what we outlined above, and then populate the &lt;code&gt;href&lt;/code&gt; and link text of our &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But what do we pass to the &lt;code&gt;xp-page-link&lt;/code&gt; attribute? A hardcoded &lt;code&gt;Guid&lt;/code&gt;, while technically correct, is going to be very difficult to understand when reading the &lt;code&gt;.cshtml&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which page does it represent 😵?&lt;/li&gt;
&lt;li&gt;Did we accidentally typo it somewhere 😖? &lt;/li&gt;
&lt;li&gt;What if we need to delete and then recreate the page we want to link to and have to replace the &lt;code&gt;Guid&lt;/code&gt; everywhere ☠?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  LinkablePage
&lt;/h3&gt;

&lt;p&gt;Instead, let's create a class with friendly names to encapsulate our identifiers:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkablePage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;LinkablePage&lt;/span&gt; &lt;span class="n"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;LinkablePage&lt;/span&gt; &lt;span class="n"&gt;ContactUs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;LinkablePage&lt;/span&gt; &lt;span class="n"&gt;PrivacyPolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;NodeGUID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;nodeGUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NodeGUID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nodeGUID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="n"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ContactUs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;PrivacyPolicy&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This &lt;code&gt;LinkablePage&lt;/code&gt; class contains the full set of any pages we might want to generate URLs for, stores the &lt;code&gt;NodeGUID&lt;/code&gt; values of each of those pages, and makes them immutable. The constructor is also &lt;code&gt;protected&lt;/code&gt;, so no new objects can be created elsewhere in the application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This last point is important because these values need to be known at development time, not runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how we'd use one with our Tag Helper:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;xp-page-link=&lt;/span&gt;&lt;span class="s"&gt;"LinkablePage.PrivacyPolicy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because these identifiers are &lt;code&gt;static&lt;/code&gt; properties, we could get fancy with our Razor and add a &lt;code&gt;@using static&lt;/code&gt; at the top of the Razor file to access the &lt;code&gt;LinkablePage&lt;/code&gt; instances directly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@using static Sandbox.Data.LinkablePage

&amp;lt;a href="" xp-page-link="PrivacyPolicy"&amp;gt;&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are both readable, and easy to author 👏🏼.&lt;/p&gt;

&lt;p&gt;Assuming the Tag Helper caches the URLs of links it generates for each of the different pages, it's performant as well 💪🏽!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The implementation for this tag helper can be found in the NuGet package for &lt;a href="https://github.com/wiredviews/xperience-page-link-tag-helpers/"&gt;Xperience Page Link Tag Helpers&lt;/a&gt;. Try it out!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  👮🏽‍♀️ Protecting Identifiers
&lt;/h2&gt;

&lt;p&gt;Now that we've leveraged this convenient and maintainable approach for generating links to pages in the content tree, how do we guarantee that these don't suddenly stop working because pages get deleted?&lt;/p&gt;

&lt;p&gt;Kentico Xperience has the perfect tool for us to leverage to guarantee the consistency of our data while the application is running - &lt;a href="https://docs.xperience.io/custom-development/handling-global-events"&gt;Global Events&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can register an event handler for the &lt;code&gt;DocumentEvents.Delete.Before&lt;/code&gt; event and then cancel the event if the Page being deleted matches a &lt;code&gt;LinkablePage&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkablePageProtectionModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Module&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;LinkablePageProtectionModule&lt;/span&gt; 
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkablePageProtectionModule&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;DocumentEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Before&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Delete_Before&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Delete_Before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DocumentEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkablePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeGUID&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeGUID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEventLogService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Cannot delete Linkable Page [&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeAliasPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;], as it might be in use. Please first remove the Linkable Page in the application code and re-deploy the application."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkablePageEventHandler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"DELETE_PAGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This class's &lt;code&gt;Delete_Before&lt;/code&gt; method will be called each time a page is deleted and it will check to make sure that page doesn't have a &lt;code&gt;NodeGUID&lt;/code&gt; that matches one of the values we've hard coded in the application. If it does match, the deletion is canceled and we log a helpful message explaining why 😉.&lt;/p&gt;

&lt;p&gt;The final step is to register our custom module in our application (preferably somewhere in our &lt;code&gt;CMSApp&lt;/code&gt; project, like a &lt;code&gt;DependencyRegistrations.cs&lt;/code&gt; file):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;RegisterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkablePageProtectionModule&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This pattern for protecting hard coded identifiers can be used for &lt;em&gt;anything&lt;/em&gt; in Xperience that supports content staging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E-Commerce data (payment methods, shipping options)&lt;/li&gt;
&lt;li&gt;Pages&lt;/li&gt;
&lt;li&gt;Custom Module class records&lt;/li&gt;
&lt;li&gt;Users/roles&lt;/li&gt;
&lt;li&gt;Categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All data our application code directly depends on through hard-coded identifiers is probably worth protecting with this approach 🤓.&lt;/p&gt;
&lt;h2&gt;
  
  
  🏁 Conclusion
&lt;/h2&gt;

&lt;p&gt;Usually we want to leave content management up to marketers and content managers (that's the whole point of a DXP!) 👍🏿.&lt;/p&gt;

&lt;p&gt;But sometimes it's far more convenient to manage some of the content ourselves - especially for links to pages that don't need to change frequently (or ever).&lt;/p&gt;

&lt;p&gt;However, we still want to make sure that these links are correct and don't break between environments or while the site is running.&lt;/p&gt;

&lt;p&gt;By storing a page's &lt;code&gt;NodeGUID&lt;/code&gt; value in the application code, and using both content staging and a custom module to prevent accidental page deletions, we can achieve all of our goals 🤗.&lt;/p&gt;

&lt;p&gt;We've created a readable, maintainable, scalable, performant, environment-consistent, and robust way of generating links to pages in our application 🥳.&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xperience.io/simplicity-through-a-unified-dxp"&gt;Kentico Xperience - DXP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/developing-websites/page-builder-development"&gt;Kentico Xperience Docs - Page Builder Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/managing-website-content/working-with-pages/managing-page-urls#ManagingpageURLs-FormerURLs"&gt;Kentico Xperience Docs - Former URLs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/deploying-websites/content-staging"&gt;Kentico Xperience Docs - Content Staging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-US/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-6.0"&gt;ASP.NET Core - Tag Helpers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wiredviews/xperience-page-link-tag-helpers/"&gt;Xperience Page Link Tag Helpers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/custom-development/handling-global-events"&gt;Kentico Xperience Docs - Global Events&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Bits of Xperience: Reducing CLS when Rendering Images</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Tue, 31 May 2022 16:10:05 +0000</pubDate>
      <link>https://forem.com/wiredviews/bits-of-xperience-reducing-cls-when-rendering-images-2nmc</link>
      <guid>https://forem.com/wiredviews/bits-of-xperience-reducing-cls-when-rendering-images-2nmc</guid>
      <description>&lt;p&gt;Kentico Xperience sites allow marketers and content managers to author content and build pages that display text, images, and anything else that HTML supports 😁.&lt;/p&gt;

&lt;p&gt;There are multiple ways to add images to content in Xperience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embed in Rich Text&lt;/li&gt;
&lt;li&gt;Custom Page Builder Component (Widget or Section)&lt;/li&gt;
&lt;li&gt;Rendered with Razor in a Page view or Template&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These images can come from multiple sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Media Library&lt;/li&gt;
&lt;li&gt;Attachments&lt;/li&gt;
&lt;li&gt;Meta files (for SKUs)&lt;/li&gt;
&lt;li&gt;Form Submissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No matter which rendering approach or content source combination we are using for images on our sites, we'll want to make sure we always render those images with the appropriate HTML attributes, and these days that includes the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;But why 🤔?&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Cumulative Layout Shift (CLS)?&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes prevent Layout Shift?&lt;/li&gt;
&lt;li&gt;How do we get this information programmatically?&lt;/li&gt;
&lt;li&gt;Why should we always retrieve image content from the database?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌊 Cumulative Layout Shift (CLS)
&lt;/h2&gt;

&lt;p&gt;Have you ever heard of &lt;a href="https://web.dev/cls/"&gt;Cumulative Layout Shift&lt;/a&gt;? It is defined (by Google) as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CLS is a measure of the largest burst of layout shift scores for every unexpected layout shift that occurs during the entire lifespan of a page.&lt;/p&gt;

&lt;p&gt;A layout shift occurs any time a visible element changes its position from one rendered frame to the next. (See below for details on how individual layout shift scores are calculated.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've all experienced this on websites. Something on the page finally loads and a button moves, or the text we're reading shifts out of the viewport.&lt;/p&gt;

&lt;p&gt;Not only is this extremely annoying 😖, but Google uses it as a quality indicator for websites. More CLS means a worse user experience and Google takes that into account for &lt;a href="https://web.dev/cls-web-tooling/"&gt;scoring in PageSpeed Insights&lt;/a&gt;, which means it impacts search ranking 😮.&lt;/p&gt;

&lt;p&gt;So, now that it's clear we want to reduce CLS on our sites, how does this factor into displaying images with Kentico Xperience?&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing CLS with Images and &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; HTML Attributes
&lt;/h2&gt;

&lt;p&gt;Images can be a major cause of CLS 🙁 because the browser doesn't know an image's dimensions until it's been fully downloaded. While the image is being downloaded, the browser's rendering engine doesn't know how much space to allocate for it on the page. Once it finishes downloading, the browser re-renders, moving other content around to give the image the space it needs 😒.&lt;/p&gt;

&lt;p&gt;It should be clear now why we shouldn't be rendering images in Xperience with &lt;em&gt;just&lt;/em&gt; a &lt;code&gt;src&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"@Model.ImagePath"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will all but guarantee CLS in most situations 😭.&lt;/p&gt;

&lt;p&gt;However, &lt;a href="https://twitter.com/jensimmons/status/1405528938147848204"&gt;browsers have been using width and height attributes&lt;/a&gt; to pre-allocate space for images for awhile now 🧐.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out this great article on the topic on Smashing: &lt;a href="https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/"&gt;Setting Height And Width On Images Is Important Again&lt;/a&gt; and it's follow up &lt;a href="https://www.smashingmagazine.com/2021/06/how-to-fix-cumulative-layout-shift-issues/#set-width-and-heights-on-images-and-iframes"&gt;How To Fix Cumulative Layout Shift (CLS) Issues&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we know the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; values for an image and add those attributes/values when rendering it, we can help prevent CLS by letting the browser know how much space (vertically) the image will need based on the image's aspect ratio computed from these two values 👏🏾.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting an Image's Dimensions in Xperience
&lt;/h2&gt;

&lt;p&gt;Thankfully, Xperience stores all uploaded image's dimensions in the database 🙏🏽. However, since there are multiple ways to store and display images in Xperience, we'll need multiple techniques for retrieving an image's dimensions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Xperience doesn't treat &lt;code&gt;.svg&lt;/code&gt; files as images, which means we need to customize its behavior to capture SVG image dimensions when they are uploaded to a site.&lt;/p&gt;

&lt;p&gt;We can use the &lt;a href="https://github.com/wiredviews/xperience-svg-media-dimensions"&gt;Xperience SVG Media Dimensions&lt;/a&gt; library to do this for us automatically!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Image Dimensions and Media Files
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.xperience.io/x/UwqRBg"&gt;Media Library&lt;/a&gt; is probably the best place to store and manage images in Xperience so we'll use it as an example 👍🏿.&lt;/p&gt;

&lt;p&gt;To select files from the Media Library for Page Type fields, you'll probably use the Media Selection Form Control:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4nDaSGoI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b6e3rujkfkk0yjpqn9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4nDaSGoI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b6e3rujkfkk0yjpqn9n.png" alt="Media Selection Form Control UI for a Page Type field" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will display a path on the page's Content tab for this control and a preview of the image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RvgSVhrp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/845c9k6tpysrpx30yy8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RvgSVhrp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/845c9k6tpysrpx30yy8s.png" alt="Page Content tab for Media Selection Form Control" width="750" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Retrieving an image from the media library by full path isn't a great developer experience, so fortunately &lt;a href="https://dev.to/mattnield"&gt;@mattnield&lt;/a&gt; wrote a &lt;a href="https://stackoverflow.com/questions/47024602/how-can-i-get-guid-from-permanent-url-media-selector/47026186#47026186"&gt;Regex several years ago&lt;/a&gt; that is able to retrieve the media file's &lt;code&gt;FileGUID&lt;/code&gt; from the path 💪🏼.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;FileGUID&lt;/code&gt; retrieved, we can query the image's content from the database:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pageRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetrieveAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
   &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TopN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
   &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; 
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeroImageMediaFilePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;mediaFileGUID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeroImageMediaFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;RegexOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IgnoreCase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;MediaFileInfo&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;mediaFileInfoProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaFileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileGUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="n"&gt;mediaFileGUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnumerableTypedResultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// use Media File values ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now that we have our full &lt;code&gt;MediaFileInfo&lt;/code&gt; we can use all of its data to render our &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element 🙌.&lt;/p&gt;

&lt;p&gt;Assuming we have an &lt;code&gt;ImageViewModel&lt;/code&gt; class defined like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageViewModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ImageViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;MediaFileInfo&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IMediaFileUrlRetriever&lt;/span&gt; &lt;span class="n"&gt;urlRetriever&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;RelativePath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;AltText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileDescription&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileTitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileImageWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileImageHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AltText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can render our image in Razor as follows:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Path"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"@Model.AltText"&lt;/span&gt;
     &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Title"&lt;/span&gt;
     &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Width"&lt;/span&gt;
     &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Height"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Awesome! Layout shift avoided and CLS reduced! 🎉🥳🎊&lt;/p&gt;
&lt;h2&gt;
  
  
  Always Retrieve the Full Image!
&lt;/h2&gt;

&lt;p&gt;You might think, "This makes sense for images at the top of the page, but what about images at the bottom? They won't impact CLS, so this is a lot of extra work for little benefit."&lt;/p&gt;

&lt;p&gt;First, if we display an image as part of a Widget, we don't know where on the page that Widget will be added and different devices' viewports might place that content in different locations on the page. We should code defensively and always retrieve these values from the database 😊.&lt;/p&gt;

&lt;p&gt;Second, and far more importantly, we should &lt;em&gt;always&lt;/em&gt; retrieve the full image content from the database for accessibility 😎!&lt;/p&gt;

&lt;p&gt;All images in Xperience have &lt;code&gt;Title&lt;/code&gt; and &lt;code&gt;Description&lt;/code&gt; fields, which translate to the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;alt&lt;/code&gt; HTML attributes. If we want accessible sites (I'd argue that &lt;a href="https://www.wiredviews.com/news/2022/03/7-things-you-can-t-skip-when-building-a-website"&gt;accessibility is one of the 7 things we can't skip&lt;/a&gt; when building a website), then we need to include these values on &lt;em&gt;every&lt;/em&gt; informational image we render (background images that are for design don't have the same requirements).&lt;/p&gt;

&lt;p&gt;This means we are going to have to retrieve the image content anyway 🤷🏽‍♀️, so it's really no extra work to get the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; values.&lt;/p&gt;
&lt;h2&gt;
  
  
  🏁 Conclusion
&lt;/h2&gt;

&lt;p&gt;Cumulative Layout Shift (CLS) is "a measure of the largest burst of layout shift scores for every unexpected layout shift that occurs during the entire lifespan of a page". This measurement represents some of the user experience visitors will have when they visit a page and can negatively impact our page's search rank (SEO) 🙄.&lt;/p&gt;

&lt;p&gt;The browser doesn't know how much space to allocate for images until they are fully downloaded, so images often impact CLS on sites. To help prevent Layout Shift for images we can add the image's &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; values as HTML attributes to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. This tells the browser how much space to allocate for the image based on its aspect ratio 🧠.&lt;/p&gt;

&lt;p&gt;To add these values with a Kentico Xperience site, we'll need to retrieve the image's content from the database (as opposed to &lt;em&gt;only&lt;/em&gt; the image's path).&lt;/p&gt;

&lt;p&gt;To improve the accessibility of our sites, we want to include &lt;code&gt;alt&lt;/code&gt; text on our images. This content, authored by content managers and marketers, is stored in the image content in the database 🤗.&lt;/p&gt;

&lt;p&gt;So, while adding &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; values to an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; might add some complexity to our code, we should be retrieving an image's content from the database anyway to ensure our sites are accessible 😉!&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/jensimmons/status/1405528938147848204"&gt;Twitter: Jen Simmons - Images and CLS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/"&gt;Smashing Magazine: Setting Height And Width On Images Is Important Again&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.smashingmagazine.com/2021/06/how-to-fix-cumulative-layout-shift-issues/"&gt;Smashing Magazine: How To Fix Cumulative Layout Shift (CLS) Issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/x/UwqRBg"&gt;Kentico Xperience: Media Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/x/LA2RBg"&gt;Kentico Xperience: Displaying content from media libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio"&gt;MDN: Aspect-ratio CSS property&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>csharp</category>
      <category>html</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Handling Failures - Return Errors, Don't Throw Them</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 14 Mar 2022 15:03:42 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa</guid>
      <description>&lt;p&gt;In a previous post we looked at how to centralize exception handling in a Kentico Xperience ASP.NET Core application by using out-of-the-box middleware. This enabled us to handle errors as a cross-cutting concern and &lt;a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;DRY&lt;/a&gt; up our code 👍🏽.&lt;/p&gt;

&lt;p&gt;However, the approach had some drawbacks. Namely, the method signatures of our code became dishonest - we had to assume that any method could throw an exception 😒.&lt;/p&gt;

&lt;p&gt;Centralized exception handling, while not bad in itself, can encourage using exception handling as control flow, which can make code much harder to reason about when reading it. This handling also tends to occur in an area of our application where it's too "late" to gracefully respond to the failure, so the best we can do is log it an display a generic error page 😔.&lt;/p&gt;

&lt;p&gt;Below we'll look at an alternative to throwing exceptions - returning errors from methods 😮.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is part 2 of a 3 part series on handling failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n"&gt;Part 1&lt;/a&gt; and &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-the-result-monad-1j25"&gt;Part 3&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What Do We Do With A Failure?&lt;/li&gt;
&lt;li&gt;Prior Art: Go&lt;/li&gt;
&lt;li&gt;Returning Errors&lt;/li&gt;
&lt;li&gt;Better Method Signatures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Do We Do With A Failure?
&lt;/h2&gt;

&lt;p&gt;One important question to ask, before we proceed further, is "What do we do with a failure?"&lt;/p&gt;

&lt;p&gt;In some applications the answer is simple - nothing! We will try to catch Exceptions in a central place in the application, log them to the &lt;a href="https://docs.xperience.io/13api/configuration/event-log"&gt;Xperience Event Log&lt;/a&gt;, and let middleware render an error page (&lt;a href="https://www.youtube.com/watch?v=UKqDStuh4fM"&gt;YOLO!&lt;/a&gt; 🤘🏾).&lt;/p&gt;

&lt;p&gt;However, in more complex situations we might need to handle errors at the method call site so we could provide fallback operations or data to ensure as much of a Page's content is rendered as possible.&lt;/p&gt;

&lt;p&gt;If we are using Exceptions to communicate failures, we're going to end up with &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; logic scattered around each of these method call sites, which is why we adopted centralized error handling in the previous post 🤦🏻‍♀️.&lt;/p&gt;

&lt;p&gt;The alternative to this &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; spaghetti is to "return" the error to the caller, handling any exceptions or failures within the method being called 🤨.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prior Art: Go
&lt;/h2&gt;

&lt;p&gt;We can look to other languages for inspiration here, since some of them, like &lt;a href="https://go.dev/blog/error-handling-and-go"&gt;Go&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html"&gt;Rust&lt;/a&gt;, treat exceptions as truly exceptional events and instead return errors from functions when there are logic violations and other failures 🤓.&lt;/p&gt;

&lt;p&gt;Go treats &lt;code&gt;string&lt;/code&gt; values as the standard error type that a function might return in case of a failure, so it's common to see a function defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"square root of negative number %g"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// implementation&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In Go, the function return type (&lt;code&gt;(float64, error)&lt;/code&gt;) comes after the parameter list. In this case, it shows &lt;code&gt;Sqrt&lt;/code&gt; will always return 2 values (which is sometimes called a &lt;a href="https://en.wikipedia.org/wiki/Tuple"&gt;couple or a tuple&lt;/a&gt;). The first item returned is "value" and the second is the "error".&lt;/p&gt;

&lt;p&gt;Looking at the function body, we can see the values we return when the input is invalid (&lt;code&gt;f &amp;lt; 0&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; - the results of the square root operation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fmt.Errorf(...)&lt;/code&gt; which returns a &lt;code&gt;string&lt;/code&gt; (as the type &lt;code&gt;error&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value is a "default" and the error is a helpful message 😉.&lt;/p&gt;

&lt;p&gt;In the successful case at the end of the function we return &lt;code&gt;val&lt;/code&gt; as the calculated value and &lt;code&gt;nil&lt;/code&gt; representing no errors.&lt;/p&gt;

&lt;p&gt;Any code that would call &lt;code&gt;Sqrt&lt;/code&gt; would check if any errors had been returned before processing the result:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Even though Go treats &lt;code&gt;string&lt;/code&gt; values as the standard error type, developers can create their own custom errors that contain more context. Callers of a function can then react to this contextual error information 🧐.&lt;/p&gt;

&lt;p&gt;Every function that can result in a failure follows this pattern, which means a typical Go program has lots of &lt;code&gt;if err != nil { ... }&lt;/code&gt; blocks in it. This might lead us to ask ourselves, "What's the benefit over exceptions if we still have to repeat guards/checks everywhere?" 😕&lt;/p&gt;

&lt;p&gt;Well, the main benefit is that the function signatures tell the call exactly what to expect. There's no surprises here! 👏🏿&lt;/p&gt;

&lt;p&gt;If your application can't ever produce errors, then don't worry, you won't need many error returning functions. If it can experience experience errors then they should be modeled and handled 👍🏼.&lt;/p&gt;

&lt;p&gt;⚠ &lt;strong&gt;Don't confuse an application that doesn't handle errors with an application that doesn't have them.&lt;/strong&gt; ⚠&lt;/p&gt;
&lt;h2&gt;
  
  
  Returning Errors
&lt;/h2&gt;

&lt;p&gt;Let's look at an example scenario for a Kentico Xperience application.&lt;/p&gt;

&lt;p&gt;In a &lt;code&gt;HomeController&lt;/code&gt; that builds a view model for our Home Page, we need to retrieve content from the database, which we will do with a &lt;code&gt;HomePageService&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HomePageService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IEventLogService&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;???&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(???));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Before we call the &lt;code&gt;HomePageService&lt;/code&gt;, we need to figure out how to design the &lt;code&gt;GetData&lt;/code&gt; method so that we don't have to worry about catching exceptions.&lt;/p&gt;

&lt;p&gt;First, we'll define the method signature to return a &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples"&gt;C# Tuple&lt;/a&gt;, which is very similar to Go's multiple-value returns for functions that can have failures.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tuples provide "concise syntax to group multiple data elements in a lightweight data structure".&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPageRetriever&lt;/span&gt; &lt;span class="n"&gt;pageRetriever&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPageUrlRetriever&lt;/span&gt; &lt;span class="n"&gt;urlRetriever&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPageDataContextRetriever&lt;/span&gt; &lt;span class="n"&gt;contextRetriever&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now for the implementation of our &lt;code&gt;GetData&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryRetrieve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Could not get HomePage Page context"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pageRetriever&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RetrieveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ProductPageIsFeatured"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HomePageFeaturedProducts"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$"Query for featured products failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;featured&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FeaturedProduct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;featured&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FeaturedProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The caller (&lt;code&gt;HomeController.Index&lt;/code&gt;) can now decide what to do if there are any errors present in the returned Tuple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="s"&gt;"HomeController"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s"&gt;"DATA_FAILURE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ServerError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Better Method Signatures
&lt;/h2&gt;

&lt;p&gt;If the C# Tuple seems a little awkward, we could instead try a C# 10 &lt;a href="https://nietras.com/2021/06/14/csharp-10-record-struct/"&gt;record struct&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We choose a &lt;code&gt;record struct&lt;/code&gt; because it has all the low memory benefits of &lt;code&gt;struct&lt;/code&gt; types and the auto-generated members of &lt;code&gt;record&lt;/code&gt; types while also having a very simple definition 😍.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This type is pretty simple, but that's really all we want (for now) if we are trying to return errors but avoid Tuples.&lt;/p&gt;

&lt;p&gt;We can update our &lt;code&gt;HomePageService.GetData()&lt;/code&gt; method to the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryRetrieve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Could not get HomePage Page context"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pageRetriever&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RetrieveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhereTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ProductPageIsFeatured"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HomePageFeaturedProducts"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"Query for featured products failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;featured&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FeaturedProduct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;featured&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FeaturedProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePageData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We still have to make the same checks in our &lt;code&gt;HomeController&lt;/code&gt;, but at least the method signature of our service has been cleaned up 🧹 and the creation of "values" and "errors" returns from our method is more explicit 💪.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;record struct&lt;/code&gt; types include a &lt;code&gt;Deconstruct&lt;/code&gt; method which adds the &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct#user-defined-types"&gt;deconstruction syntax&lt;/a&gt; that Tuples have. This means our custom &lt;code&gt;Result&lt;/code&gt; type can use the same syntax as the tuple in our &lt;code&gt;HomeController&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've reviewed the problems that come with handling errors in our application with only global exception handling. We end up with dishonest method signatures that we can't trust and cannot respond to errors where we need to 😑.&lt;/p&gt;

&lt;p&gt;By following the pattern of returning errors, like the programming languages Go and Rust, we end up with a much more understandable and readable code base. We encourage modeling the potential error states of our application, giving ourselves the opportunity to handle them 😄.&lt;/p&gt;

&lt;p&gt;While the C# tuple syntax has its uses, using it to model application errors might not be the best. So instead we can create a simple container type - &lt;code&gt;Result&lt;/code&gt; - to make our method signatures more legible 😀. Unfortunately this still leaves us with a bunch of procedural &lt;code&gt;if (error is not null ...) { ... }&lt;/code&gt; checks in our application 😣.&lt;/p&gt;

&lt;p&gt;In my next post, we'll look at a way of returning failures so we can respond to them intelligently, while avoiding both complex method signatures and procedural conditional checks everywhere by using the &lt;code&gt;Result&lt;/code&gt; &lt;a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;monad&lt;/a&gt; 🤔.&lt;br&gt;
...&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;Don't Repeat Yourself&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/13api/configuration/event-log"&gt;Xperience Event Log API Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/error-handling-and-go"&gt;Go Docs: Error handling and Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html"&gt;Rust Docs: Recoverable Errors with Result&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Tuple"&gt;Wikipedia: Tuple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples"&gt;C# Docs: Tuple types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nietras.com/2021/06/14/csharp-10-record-struct/"&gt;C# 10 Record Struct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct#user-defined-types"&gt;C# Deconstruct Custom Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;Wikipedia: Monad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;





&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>aspnetcore</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Kentico Xperience Xplorations: Using Tailwind CSS with Kentico Xperience</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 24 Jan 2022 15:15:11 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-xplorations-using-tailwind-css-with-kentico-xperience-2mi6</link>
      <guid>https://forem.com/seangwright/kentico-xperience-xplorations-using-tailwind-css-with-kentico-xperience-2mi6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In this post we'll be exploring what it looks like to use &lt;a href="https://tailwindcss.com/"&gt;Tailwind CSS&lt;/a&gt; in our &lt;a href="https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core"&gt;Kentico Xperience&lt;/a&gt; applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧭 Starting Our Journey: Strategies for CSS
&lt;/h2&gt;

&lt;p&gt;From Tailwind CSS's home page, it defines itself as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A utility-first CSS framework packed with classes like &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;pt-4&lt;/code&gt;, &lt;code&gt;text-center&lt;/code&gt; and &lt;code&gt;rotate-90&lt;/code&gt; that can be composed to build any design, directly in your markup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But, before we dive into setting up and using Tailwind CSS with Kentico Xperience, let's explore &lt;em&gt;why&lt;/em&gt; 🤔 we would use it instead of other approaches to CSS development.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔬 The True Nature of CSS
&lt;/h3&gt;

&lt;p&gt;CSS, as a language, is declarative and global. Developers that are new to CSS or more comfortable in JavaScript often dislike CSS' declarative and global nature.&lt;/p&gt;

&lt;p&gt;However, if we look at things holistically, we can see that being global is what enables the power of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade"&gt;the cascade&lt;/a&gt;, and declarative programming provides a powerful API that hides away the &lt;a href="https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/"&gt;brain 🧠 melting 😱 complexity&lt;/a&gt; of browser rendering engines.&lt;/p&gt;

&lt;p&gt;That said, CSS does need to be managed, especially in larger projects that are expected to evolve (and be maintainable) over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔩 CSS Utility Classes
&lt;/h2&gt;

&lt;p&gt;One of my favorite blog posts ever about CSS and HTML is &lt;a href="https://twitter.com/adamwathan"&gt;Adam Wathan's&lt;/a&gt; post &lt;a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/"&gt;CSS Utility Classes and "Separation of Concerns"&lt;/a&gt; 🤩.&lt;/p&gt;

&lt;p&gt;Adam Wathan is the creator of Tailwind CSS, and an experienced web developer, so it's probably worth checking out his perspective on CSS if only to broaden our own... 🧐&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I recommend following the link above and reading the post &lt;strong&gt;in full&lt;/strong&gt;. Using Tailwind CSS has, unfortunately, become a very polarizing topic, I think primarily because developers often express their opinions without expressing the context that leads to those opinions. Do yourself a favor 👍🏽 and gain a little context!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Adam reflects on the CSS/HTML "separation of concerns" in his post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you think about the relationship between HTML and CSS in terms of "separation of concerns", it's very black and white.&lt;/p&gt;

&lt;p&gt;You either have separation of concerns (good!), or you don't (bad!).&lt;/p&gt;

&lt;p&gt;This is not the right way to think about HTML and CSS.&lt;/p&gt;

&lt;p&gt;Instead, think about dependency direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS that depends on HTML&lt;/strong&gt;.&lt;br&gt;
Naming your classes based on your content (like .author-bio) treats your HTML as a dependency of your CSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML that depends on CSS&lt;/strong&gt;.&lt;br&gt;
Naming your classes in a content-agnostic way after the repeating patterns in your UI (like .media-card) treats your CSS as a dependency of your HTML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He then poses this comparison:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CSS Zen Garden takes the first approach, while UI frameworks like Bootstrap or Bulma take the second approach.&lt;/p&gt;

&lt;p&gt;Neither is inherently "wrong"; it's just a decision made based on what's more important to you in a specific context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And, asks a simple question ❔:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For the project you're working on, what would be more valuable: restyleable HTML, or reusable CSS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal of having more reusable CSS at the cost of re-stylable HTML is where Tailwind CSS as a framework comes from.&lt;/p&gt;

&lt;p&gt;If this aligns with the goals of our project, then Tailwind might be a good fit 😃, and we can continue reading to learn how to setup Tailwind CSS with a Kentico Xperience ASP.NET Core project...&lt;/p&gt;

&lt;h2&gt;
  
  
  🗺 Kentico Xperience + Tailwind CSS
&lt;/h2&gt;

&lt;p&gt;Tailwind CSS can be used by loading its CSS &lt;a href="https://tailwindcss.com/docs/installation/play-cdn"&gt;from a CDN&lt;/a&gt; but as noted in the documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Play CDN is designed for development purposes only, and is not the best choice for production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, instead, we're going to focus on using &lt;a href="https://tailwindcss.com/docs/installation"&gt;Tailwind as a Node.js CLI tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can follow the documentation in the previous link with a few minor annotations and additions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: These steps assume you are comfortable using the &lt;a href="https://docs.microsoft.com/en-us/windows/terminal/"&gt;command line&lt;/a&gt;, have &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; installed, and have some familiarity with client-side tools and development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First, we'll want to make sure we've changed directory to our Kentico Xperience ASP.NET Core projects at the command line because our Tailwind integration is only for the content presentation (ASP.NET Core) application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; .&lt;span class="se"&gt;\S&lt;/span&gt;andbox.Web&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Sandbox.Web
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then we install our Node.js Tailwind CSS dependency with npm:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once this installation completes we can initialize our Tailwind configuration:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npx tailwindcss init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Tailwind's build process generates a CSS file with the minimal number of CSS classes and style rules possible by scanning the HTML templates of an application and only adding the utility classes used in those templates 🤓.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;tailwind.config.js&lt;/code&gt;, we customize it for our ASP.NET Core specific use-case by telling Tailwind it should look for its CSS utility classes in all of our Razor files:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./**/*.{cshtml}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;  
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We then create an entry-point &lt;code&gt;site.css&lt;/code&gt; file at the root of our ASP.NET Core project where we specify the parts of Tailwind CSS we'd like to include in our project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: You can also use this entry-point file to define custom CSS classes. However, I'd encourage you to attempt to read &lt;a href="https://tailwindcss.com/docs/reusing-styles"&gt;Tailwind's documentation on CSS reusability&lt;/a&gt; and try to meet your CSS requirements with the library's utility classes first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we update our &lt;code&gt;package.json&lt;/code&gt; with some scripts to run the Tailwind command on our project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss -i ./site.css -o ./wwwroot/site.css --watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tailwindcss -i ./site.css -o ./wwwroot/site.css --minify"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We tell Tailwind to output the generated CSS file into the &lt;code&gt;./wwwroot&lt;/code&gt; folder because &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-6.0&amp;amp;tabs=windows#web-root"&gt;it is the only directory that ASP.NET Core serves static files&lt;/a&gt; from by default.&lt;/p&gt;

&lt;p&gt;It's also convenient that all files in &lt;code&gt;./wwwroot&lt;/code&gt; will automatically be published with the application without any additional build configuration.&lt;/p&gt;

&lt;p&gt;There are 2 "scripts", &lt;code&gt;start&lt;/code&gt; which we can run with &lt;code&gt;npm start&lt;/code&gt;, at the command line, for local development, and &lt;code&gt;build&lt;/code&gt; which we run with &lt;code&gt;npm run build&lt;/code&gt; for production.&lt;/p&gt;

&lt;p&gt;Last, we'll want to integrate this npm tooling into MSBuild so that when our ASP.NET Core application is built in &lt;code&gt;Release&lt;/code&gt; mode, the production-ready CSS is also generated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This can be very helpful if we are using CI/CD for build and deployment of our Kentico Xperience application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll make the following addition to the bottom of our ASP.NET Core's &lt;code&gt;.csproj&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk.Web"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- other settings --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"NpmInstall"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"BuildCSS"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"npm ci"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"BuildCSS"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"BeforeBuild"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"npm run build"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Whenever a &lt;code&gt;Release&lt;/code&gt; build of our application is initiated, first &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-ci"&gt;npm ci&lt;/a&gt; will be run first, followed by &lt;code&gt;npm run build&lt;/code&gt; to kick off our CSS production build command 👏🏾.&lt;/p&gt;
&lt;h2&gt;
  
  
  🌵 Ouch: Kentico Xperience + Tailwind CSS Pitfalls
&lt;/h2&gt;
&lt;h3&gt;
  
  
  🥊 Visual Studio vs VS Code
&lt;/h3&gt;

&lt;p&gt;Tailwind CSS includes thousands of permutations of utility CSS classes, which creates a CSS API of sorts that developers have to learn to be effective with the library.&lt;/p&gt;

&lt;p&gt;It's &lt;a href="https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code"&gt;recommended&lt;/a&gt; to use an editor when authoring HTML that supports intellisense for Tailwind CSS class names in our HTML and PostCSS's special syntax when writing custom CSS rules.&lt;/p&gt;

&lt;p&gt;Currently, &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt; (or &lt;a href="https://www.jetbrains.com/rider/"&gt;Rider&lt;/a&gt;) is the best option 😎. Visual Studio has no extension support 😑.&lt;/p&gt;

&lt;p&gt;However, Visual Studio is a best-in-class IDE for editing C# and Razor, so developers will have to choose whether they want to prioritize the authoring experience for back-end C# or their front-end Tailwind CSS 🤷‍♂️.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think front-end tooling is an area where Visual Studio is currently very weak compared to other products, but if your projects don't have much in the way of front-end code and dependencies, this might not matter to you.&lt;/p&gt;

&lt;p&gt;My personal preference is to use VS Code as much as I can for any front-end development. I write a lot of TypeScript, JavaScript, SCSS, and use lots of npm scripts and VS Code extensions. I even &lt;em&gt;exclude&lt;/em&gt; front-end files from the .NET project so that I can't see them in Visual Studio 😉. This forces me to use VS Code (and all of its wonderful extensions and tooling) for this part of a project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  🛠 Dynamic CSS Class Generation
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, Tailwind CSS scans our project HTML template source files for recognizable CSS classes and then includes those in the generated output CSS file.&lt;/p&gt;

&lt;p&gt;However, Tailwind &lt;a href="https://tailwindcss.com/docs/content-configuration#dynamic-class-names"&gt;cannot recognize dynamically composed CSS classes&lt;/a&gt;, which means we need to consider how creative we get in our Razor files when building our HTML templates.&lt;/p&gt;

&lt;p&gt;For example, the following won't work because Tailwind needs to statically recognize at development time what classes we are using - it can't determine what will be used at runtime 😔:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@{
   string classColor = string.Equals(color, "red")
      ? "red"
      : "blue";
}

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"@(classColor + "&lt;/span&gt;&lt;span class="na"&gt;-500&lt;/span&gt;&lt;span class="err"&gt;")"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We'd need to use the following to get the desired result:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@{
   string classColor = string.Equals(color, "red")
      ? "red-500"
      : "blue-500";
}

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"@classColor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This can feel like a hoop we have to jump through to get Tailwind to work correctly, and in a way, it is. But this extra work means the final production-ready CSS file is extremely small and optimized 💪🏾.&lt;/p&gt;

&lt;p&gt;As a final workaround, we can &lt;a href="https://tailwindcss.com/docs/content-configuration#safelisting-classes"&gt;safelist&lt;/a&gt; specific classes we know are being used but Tailwind can't find when it scans our templates. This should be avoided if possible because it means we could be shipping unused CSS if we forget to keep the safelist up to date 😣.&lt;/p&gt;
&lt;h3&gt;
  
  
  👩🏽‍💻 Referencing CSS Classes in CSharp
&lt;/h3&gt;

&lt;p&gt;Kentico Xperience's Page Builder components (&lt;a href="https://docs.xperience.io/developing-websites/form-builder-development/assigning-editing-components-to-properties"&gt;Widgets, Sections, Page Template Properties&lt;/a&gt;) let us provide Content Managers with many customization points to build Pages that are flexible in both content and design 😎.&lt;/p&gt;

&lt;p&gt;Unfortunately, if we aren't careful we could end up referencing Tailwind classes in places that aren't visible to Tailwind's tooling.&lt;/p&gt;

&lt;p&gt;If we had some Page Template Properties that let a Content Manager customize the design of a page, we could back ourselves into a corner:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageTemplateProperties&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPageTemplateProperties&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;EditingComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DropDownComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDENTIFIER&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;EditingComponentProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DropDownProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="s"&gt;"bg-green-300;Green\r\nbg-red-500;Red"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;BackgroundColor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These properties reference the Tailwind classes, but since this code is in a C# file and Tailwind is configured to scan &lt;code&gt;.cshtml&lt;/code&gt; files, it won't ever detect that our site is using them and they won't be included in the generated CSS 😕.&lt;/p&gt;

&lt;p&gt;However, even if we weren't using Tailwind CSS storing CSS classes in component Properties &lt;strong&gt;is a bad idea&lt;/strong&gt;. The approach above ties your component to a very specific design, makes this component much less reusable, and makes future design updates more difficult.&lt;/p&gt;

&lt;p&gt;Instead, we should store values that represent our design choices and leave the selection of the actual CSS class up to the Razor View. Even better, avoid the specific color values and use more semantic terms 😊:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePageTemplateProperties&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPageTemplateProperties&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;EditingComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DropDownComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDENTIFIER&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;EditingComponentProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DropDownProperties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataSource&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="s"&gt;"primary;Primary\r\nsecondary;Secondary"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;BackgroundColor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, in our Razor View we can render the CSS class that matches the chosen option:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@{
    string backgroundColor = Model.BackgroundColor switch
    {
        "primary" =&amp;gt; "bg-green-300",
        "secondary" =&amp;gt; "bg-red-500",
        _ =&amp;gt; ""
    };
}

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"@backgroundColor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  💻 CSS Classes in the Database
&lt;/h3&gt;

&lt;p&gt;I imagine a lot of you have already been thinking "How do we handle CSS classes that are stored in the database?" It's easy enough to take the semantic approach we just looked at and use it for Page Type fields and CMS Settings...&lt;/p&gt;

&lt;p&gt;But, what about Rich Text?&lt;/p&gt;

&lt;p&gt;Yeah, &lt;a href="https://youtu.be/b7cYeIL1ZcU"&gt;Rich Text is problematic for many reasons&lt;/a&gt;, but it's a requirement that most sites can't avoid.&lt;/p&gt;

&lt;p&gt;Even if we were not using a CSS framework like Tailwind, we'd run into issues maintaining our custom CSS while having to support Rich Text 😮.&lt;/p&gt;

&lt;p&gt;It's a lot easier to do a search in an IDE for a character sequence to see if a CSS class is still being used (and how it's being used) than querying dozens of database tables and columns looking for the same information in Rich Text content.&lt;/p&gt;

&lt;p&gt;This is probably one situation where using Tailwind's escape hatches is the best option and we can follow these tips to avoid any real headaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply"&gt;Use @apply&lt;/a&gt; to compose Tailwind's utility classes into custom CSS rules&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/content-configuration#safelisting-classes"&gt;Safelist&lt;/a&gt; and CSS classes we know will show up in Rich Text (even if they appear in our HTML templates).&lt;/li&gt;
&lt;li&gt;Encourage Content Managers to use Rich Text to structure free-form content and only apply styling minimally (this should always be encouraged)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-the-rich-text-editor-for-page-builder/customizing-the-rich-text-editor-toolbar"&gt;Customize the Rich Text editor&lt;/a&gt; in Xperience to create UI elements that help Content Managers use the correct CSS classes on their Rich Text if they are required&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ✈ Heading Home: Is Xperience + Tailwind CSS a Good Idea?
&lt;/h2&gt;

&lt;p&gt;As we've seen, setting up Tailwind CSS in a Kentico Xperience ASP.NET Core project is fairly simple, so when considering whether or not we should use Tailwind for our project, it's more a question of maintenance and growth than getting started.&lt;/p&gt;

&lt;p&gt;Tailwind CSS is a powerful tool designed for a specific scenario - web applications where reusable CSS is more important than re-styleable HTML 🧐.&lt;/p&gt;

&lt;p&gt;If this doesn't match our project, then Tailwind might not be the best tool for helping us author and maintain CSS. If it is, then there are a lot of benefits it can bring to our development experience 🤠.&lt;/p&gt;

&lt;p&gt;However, there are also quite a few pitfalls we saw when exploring how we might use Tailwind with Kentico Xperience's features 😬.&lt;/p&gt;

&lt;p&gt;We saw there are solutions to these potential problems, and that these solutions aren't only useful with Tailwind CSS - they are good practices for managing design and content in any Kentico Xperience site. It just so happens that with Tailwind they become more apparent.&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;


Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/"&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade"&gt;CSS Cascade&lt;/a&gt; on MDN&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/"&gt;Inside a super fast CSS engine: Quantum CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/adamwathan"&gt;Adam Wathan&lt;/a&gt; on Twitter&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/"&gt;CSS Utility Classes and "Separation of Concerns"&lt;/a&gt; 🌟&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/installation/play-cdn"&gt;Using Tailwind via CDN&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/installation"&gt;Using Tailwind via CLI&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/windows/terminal/"&gt;Windows Terminal&lt;/a&gt; on Microsoft Docs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/"&gt;Download Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/reusing-styles"&gt;Reusing Styles with Tailwind CSS&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-6.0&amp;amp;tabs=windows#web-root"&gt;ASP.NET Core Fundamentals: Webroot&lt;/a&gt; on Microsoft Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-ci"&gt;Npm ci&lt;/a&gt; on npm Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code"&gt;Editor Setup&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/"&gt;VS Code Download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.jetbrains.com/rider/"&gt;JetBrains Riders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/content-configuration#dynamic-class-names"&gt;Dynamic Class Names&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.com/docs/content-configuration#safelisting-classes"&gt;Safelisting Classes&lt;/a&gt; on Tailwind Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/developing-websites/form-builder-development/assigning-editing-components-to-properties"&gt;Assigning Editing Components to Properties&lt;/a&gt; on Kentico Xperience Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/b7cYeIL1ZcU"&gt;Content Driven Development: Or, Why the WYSIWYG Can Be a Trap (Video)&lt;/a&gt; from Kentico Xperience Connections 2020&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-the-rich-text-editor-for-page-builder/customizing-the-rich-text-editor-toolbar"&gt;Configuring the Rich Text Editor&lt;/a&gt; on Kentico Xperience Docs&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/10963"&gt;Kentico Xperience Design Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>css</category>
      <category>aspnetcore</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Kentico Xperience Design Patterns: Handling Failures - Centralized Exception Handling</title>
      <dc:creator>Sean G. Wright</dc:creator>
      <pubDate>Mon, 08 Nov 2021 14:54:04 +0000</pubDate>
      <link>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n</link>
      <guid>https://forem.com/seangwright/kentico-xperience-design-patterns-handling-failures-centralized-exception-handling-4p3n</guid>
      <description>&lt;p&gt;Even perfectly crafted code can result in runtime failures for reasons that are out of our control 😫. Flaky network connections, data schemas that don't match the deployed code, or the accidental deletion of required data.&lt;/p&gt;

&lt;p&gt;As software engineers, we are expected to design for the happy 😊 paths &lt;em&gt;and&lt;/em&gt; the 'exceptional' unhappy ☹ ones.&lt;/p&gt;

&lt;p&gt;Let's look at a common approach for handling these failures in ASP.NET Core applications, how it applies to Kentico Xperience sites, and where it falls short.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is part 1 of a 3 part series on handling failures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-return-errors-dont-throw-them-4apa"&gt;Part 2&lt;/a&gt; and &lt;a href="https://dev.to/seangwright/kentico-xperience-design-patterns-handling-failures-the-result-monad-1j25"&gt;Part 3&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 What Will We Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Representing failures as Exceptions&lt;/li&gt;
&lt;li&gt;How to handle Exceptions&lt;/li&gt;
&lt;li&gt;Exception handling as a cross-cutting concern&lt;/li&gt;
&lt;li&gt;The problems with using Exceptions as control flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Representing Failures as Exceptions
&lt;/h2&gt;

&lt;p&gt;.NET developers are familiar with exceptions. They are part of the &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/framework-libraries#base-class-library"&gt;Base Class Library&lt;/a&gt; (BCL) and can be used to signal that applications have entered into an invalid state, by using the &lt;code&gt;throw new Exception()&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;However, throwing exceptions is just half of the story. We can also catch exceptions by wrapping some code in the &lt;code&gt;try/catch&lt;/code&gt; syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;someBusinessOperation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// log and handle the exception&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Of course, to be able to do the correct thing with a caught exception, we need to know 🧐 what type of exception it is.&lt;/p&gt;

&lt;p&gt;We can use the built-in exceptions, like &lt;code&gt;ArgumentNullException&lt;/code&gt; and &lt;code&gt;InvalidOperationException&lt;/code&gt;, or even the basic &lt;code&gt;Exception&lt;/code&gt; type to halt execution when something's gone wrong.&lt;/p&gt;

&lt;p&gt;We can also create our own custom exception types, like the following &lt;code&gt;BadRequestException&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BadRequestException&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BadRequestException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But why go through the effort of making a custom exception type?&lt;/p&gt;

&lt;p&gt;Custom exceptions can more closely model our application's domain language and use-cases. The exception types in the BCL have to be use-case agnostic, by definition, because they are 'common' and usable in &lt;em&gt;any&lt;/em&gt; .NET application 🤔.&lt;/p&gt;

&lt;p&gt;If we were to use the base &lt;code&gt;Exception&lt;/code&gt; type for all of our applications exceptions, it's going to be hard to determine what we should do when we catch the exception.&lt;/p&gt;

&lt;p&gt;When we create domain specific exception types, we can use &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling#catch-blocks"&gt;exception filters&lt;/a&gt; - a specific kind of C# pattern matching used with &lt;code&gt;catch&lt;/code&gt; statements 🤓 - to make sure we execute specific code when different kinds of exceptions happen.&lt;/p&gt;

&lt;p&gt;Imagine this scenario...&lt;/p&gt;

&lt;p&gt;In a Kentico Xperience application, we have site-wide settings in a custom Page Type, which we'll call &lt;code&gt;SiteContent&lt;/code&gt;. If this &lt;code&gt;SiteContent&lt;/code&gt; Page is deleted from the Content Tree, how should our application respond 🤷🏽‍♂️?&lt;/p&gt;

&lt;p&gt;When we try to query for the &lt;code&gt;SiteContent&lt;/code&gt; Page, we could throw a custom &lt;code&gt;MissingContent&lt;/code&gt; exception if it's not found.&lt;/p&gt;

&lt;p&gt;Here's our example exception type:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MissingContentException&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;PageType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MissingContentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pageType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Could not find [&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pageType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;PageType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And, here's our content querying logic:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pageRetriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetrieveAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SiteContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MissingContentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SiteContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLASS_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The nice thing about throwing an exception is that it lets us abort our application execution immediately, preventing additional code errors or failures 💪🏾.&lt;/p&gt;

&lt;p&gt;We also don't have to change the type signature of our methods that throw exceptions. This is convenient for blocks of code that could fail for any number of reasons. Our method return type represents the happy path, and the exceptions which the method throws represent all the failure scenarios 😏.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Handle Exceptions
&lt;/h2&gt;

&lt;p&gt;If we throw an exception somewhere, we need to eventually catch that exception and respond accordingly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;siteContent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetSiteContentPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MissingContentException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="nf"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SiteContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLASS_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// log and handle&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this approach we're representing our failed operation (querying for the SiteContent Page) with a custom exception type, and only catching and responding to the exceptions we are interested in - all other exceptions will need to be caught at some higher level in our code.&lt;/p&gt;

&lt;p&gt;This all leads to the question - how do we want to handle exceptions we've caught? What information do we want to collect and what do we want to present to the site visitor that was unfortunate enough to trigger this failure 🤔?&lt;/p&gt;

&lt;p&gt;In a Kentico Xperience site, the most common way of handling exceptions is going to be logging the failure to the Xperience &lt;a href="https://docs.xperience.io/13api/configuration/event-log"&gt;Event Log&lt;/a&gt; and displaying an error page to the visitor 👍🏻.&lt;/p&gt;

&lt;p&gt;We can usually achieve this with two middlewares - &lt;code&gt;UseStatusCodePagesWithReExecute()&lt;/code&gt; and &lt;code&gt;UseExceptionHandler()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Status Code Pages With Re-Execute
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;UseStatusCodePagesWithReExecute&lt;/code&gt; will handle results from Controllers that have 'error' HTTP status codes (eg 400, 500) and let you execute another route, passing the status code as a parameter:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// earlier middleware&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStatusCodePagesWithReExecute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"/status-code-error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s"&gt;"?code={0}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// later middleware&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The site visitor won't see &lt;code&gt;/status-code-error&lt;/code&gt; in their address bar 🤨, but the Controller action associated with that route can return some content explaining to the user what happened, based on the &lt;code&gt;code&lt;/code&gt; query parameter. (&lt;code&gt;/status-code-error&lt;/code&gt; can be any path as long as it's one your application can handle).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read another explanation on these middlewares &lt;a href="https://stackoverflow.com/a/51118918/939634"&gt;on StackOverflow&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This middleware will typically be used when we've caught an exception later in the request pipeline - like in an MVC filter or Controller action - and returned a result representing the error, like &lt;code&gt;StatusCode()&lt;/code&gt; or &lt;code&gt;NotFound()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;myService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HomeViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;eventLogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogException&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: The &lt;code&gt;UseStatusCodePagesWithReExecute&lt;/code&gt; middleware does &lt;em&gt;not&lt;/em&gt; catch exceptions 😲. It helps to keep the presentation of 'error' status codes separate from the Controllers that return them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While this lets us handle and log each exception exactly as we'd like for every Controller action, it's also a lot of repeated code when our Kentico Xperience site grows beyond a few Pages 😬.&lt;/p&gt;

&lt;p&gt;Fortunately, there's a way handle exceptions as a cross-cutting concern... 😅&lt;/p&gt;
&lt;h2&gt;
  
  
  Exception Handling as a Cross-Cutting Concern
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Exception Handler
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;UseExceptionHandler&lt;/code&gt; middleware wraps the entire request pipeline in a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt;, which means any unhandled exceptions will be caught and the developer can specify how they should be handled - like executing a request to another path in the application:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This is the first in / last out middleware&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ExceptionHandlerOptions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AllowStatusCode404Response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ExceptionHandlingPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/error"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// later middleware&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this example, when an exception is caught the site will execute the &lt;code&gt;/error&lt;/code&gt; path, which is typically a Controller action, and return its result.&lt;/p&gt;

&lt;p&gt;We'll often use the &lt;code&gt;UseExceptionHandler&lt;/code&gt; and &lt;code&gt;UseStatusCodePagesWithReExecute&lt;/code&gt; middleware together. The former might return a 'Not Found' Page, keeping the HTTP status code as 404, and we wouldn't want that to be treated as an unhandled exception for the &lt;code&gt;UseExceptionHandler&lt;/code&gt; middleware to process. &lt;code&gt;AllowStatusCode404Response = true&lt;/code&gt; ensures the 404 response is allowed through 😮.&lt;/p&gt;

&lt;p&gt;The Controller that handles our failure can grab the original &lt;code&gt;Exception&lt;/code&gt; and request data out of the &lt;code&gt;IExceptionHandlerPathFeature&lt;/code&gt;, which is where the middleware stores it before calling the Controller action:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;exceptionFeature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IExceptionHandlerPathFeature&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

       &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exceptionFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;originalPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exceptionFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="n"&gt;eventLogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="s"&gt;"ErrorController"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
           &lt;span class="s"&gt;"ASPNET_EXCEPTION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="n"&gt;BadRequestException&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The page had some problems"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="n"&gt;NotAuthorizedException&lt;/span&gt; &lt;span class="n"&gt;na&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You don't have permissions to view this"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"We've hit a snag loading the page!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="p"&gt;};&lt;/span&gt;

       &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ErrorViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this example, we are logging the exception in Xperience's Event Log, creating a friendly, contextual message and HTTP status code based on the type of exception that was caught, and returning a View to the user - all under the URL that caused the original problem - no redirects here 👏🏾!&lt;/p&gt;

&lt;p&gt;We've effectively created a central cross-cutting Global Exception Handler that guarantees &lt;em&gt;any&lt;/em&gt; exception thrown in our application will be caught, logged, handled, and presented to the visitor as a normal site Page - not an incomprehensible 'developer' error Page.&lt;/p&gt;

&lt;p&gt;This seems great... 😁 doesn't it 😟!?&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problems with Using Exceptions as Control Flow
&lt;/h2&gt;

&lt;p&gt;The convenience of throwing (or allowing) exceptions anywhere in our application and then using a Global Exception Handler comes with a real cost and a potential cost.&lt;/p&gt;
&lt;h3&gt;
  
  
  It's To Late, Baby
&lt;/h3&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/VkKxmnrRVHo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The real cost is that by the time we're at the Exception Handler, it's too late to do anything to recover. We've consigned ourselves to a failed request and the best we can give to the site visitor is a friendly error and log the Exception.&lt;/p&gt;

&lt;p&gt;The problem is not all exceptions are created equal 😕.&lt;/p&gt;

&lt;p&gt;What if an exception was thrown trying to get the URL for an image in an image gallery on the Page? Do we want to throw away everything else that was correctly retrieved from the database and show an error page, just because of this single failure? Probably not.&lt;/p&gt;

&lt;p&gt;Building a Kentico Xperience application is about choosing the best ways to manage and present content in the system, and keep visitors coming back to the site. We need to more nuanced 😎 with handling failures and treat our approach as a spectrum - some failures are ok, others are not, some require backup content, others can just result in content missing from the Page.&lt;/p&gt;

&lt;p&gt;Unfortunately, we lose the ability to be more nuanced if we turn every failure into an exception and then rely on a single, late (in the request lifecycle) point to deal with them.&lt;/p&gt;

&lt;p&gt;Exception handling is a cross-cutting concern (like authorization, logging, or caching), which aligns with the &lt;code&gt;UseExceptionHandler&lt;/code&gt; middleware 👍. At the same time, cross-cutting concerns should be applied with multiple layers, not as a single blanket thrown over the application to hide the ugly parts 😏.&lt;/p&gt;
&lt;h3&gt;
  
  
  Abusing Control Flow
&lt;/h3&gt;

&lt;p&gt;While some exceptions represent application states that should immediately halt the request, many are business logic situations we can gracefully (or at least intelligently) recover from.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647790(v=pandp.10)#exception-management"&gt;many&lt;/a&gt; &lt;a href="https://fsharpforfunandprofit.com/posts/exceptions/#should-functions-throw-exceptions-or-return-error-structures"&gt;articles&lt;/a&gt; &lt;a href="https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/"&gt;about using exceptions&lt;/a&gt; &lt;a href="https://headsigned.com/posts/stop-using-exceptions-for-flow-control/"&gt;to control application flow&lt;/a&gt; ... most recommend against it.&lt;/p&gt;

&lt;p&gt;If you choose not to consider this advice, problems will show up in your code base over time 😧.&lt;/p&gt;

&lt;p&gt;A developer throws an exception when an array index is a negative number. Then, another developer later throws an exception when the credentials submitted by the login form are invalid. Eventually, half the methods in your app throw exceptions, either directly, or by some method they call, and all of those method signatures have become &lt;em&gt;untrustworthy&lt;/em&gt; 😑.&lt;/p&gt;

&lt;p&gt;What do you think this method does?&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;GetUserRoles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It probably returns all the roles a User is in when the current request is from an authenticated user. But what about when the user hasn't finished sign-up? Or if the request is for an unauthenticated User 🤔?&lt;/p&gt;

&lt;p&gt;We'd hope it would return an empty array or maybe an array with a &lt;code&gt;"Anonymous"&lt;/code&gt; Role... but there's nothing preventing it from throwing an exception! Even if it didn't, it could call other methods and they could throw exceptions!&lt;/p&gt;

&lt;p&gt;Method return types should tell you what happens during normal use, and exceptional cases should really be &lt;em&gt;exceptional&lt;/em&gt;. We want to trust 🤗 the code we write and the code written by others.&lt;/p&gt;

&lt;p&gt;Using exceptions as control flow is a leaky abstraction because it forces us to know a lot more about a method than just its signature - potentially its internals, which breaks the idea of encapsulation. It also slowly reinforces our reliance on the Global Exception Handler pattern since &lt;em&gt;any&lt;/em&gt; code in our application can &lt;code&gt;throw&lt;/code&gt; without us knowing 🤦‍♀️!&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In a Kentico Xperience site, we will inevitably run into situations that cause our code to fail to produce the desired result. These could be from exceptions thrown by libraries we are using, or our own code, or they could be from data not matching what we require 🤷🏽‍♂️.&lt;/p&gt;

&lt;p&gt;In these cases we can use exceptions to quickly stop the current execution and bubble 🧼 that failure up to another part of our application that is responsible for catching them.&lt;/p&gt;

&lt;p&gt;If we make custom exception types, we can match them to the specific scenarios our sites might encounter. And, we can react to each type of exception intelligently 🧐.&lt;/p&gt;

&lt;p&gt;In order to not repeat our &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; logic across the entire code base, we can centralize exception handling with the &lt;code&gt;UseExceptionHandler&lt;/code&gt; ASP.NET Core middleware. This gives us a convenient place to convert uncaught exceptions into friendly error pages and log entries 👍🏿.&lt;/p&gt;

&lt;p&gt;This convenience comes with some negatives. Our centralized exception handling will often not be located where we need it to if we want to compensate for some failures and let the request continue processing. It also encourages using exceptions as control flow, which results in method signatures only representing the 'happy path' and code that is harder to understand and trust 😖.&lt;/p&gt;

&lt;p&gt;In my next post, we'll look at a way of including failures in our method signatures/return types, which keeps our code honest 😇 and allows us to better handle failures.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;As always, thanks for reading 🙏!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/framework-libraries#base-class-library"&gt;.NET Base Class Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling#catch-blocks"&gt;C# Exception Filters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.xperience.io/13api/configuration/event-log"&gt;Kentico Xperience Event Log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/a/51118918/939634"&gt;Status Code vs Exception Handling middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647790(v=pandp.10)#exception-management"&gt;.NET Documentation - Exception Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/posts/exceptions/#should-functions-throw-exceptions-or-return-error-structures"&gt;Exceptions vs Error Structures in F#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://enterprisecraftsmanship.com/posts/exceptions-for-flow-control/"&gt;Exceptions for Control Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://headsigned.com/posts/stop-using-exceptions-for-flow-control/"&gt;Stop Using Exceptions for Control Flow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


Photo by &lt;a href="https://unsplash.com/@jordanmadrid"&gt;Jordan Madrid
&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;





&lt;p&gt;We've put together a list over on &lt;a href="https://github.com/Kentico/Home/blob/master/RESOURCES.md"&gt;Kentico's GitHub account&lt;/a&gt; of developer resources. Go check it out!&lt;/p&gt;

&lt;p&gt;If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.&lt;/p&gt;


&lt;div class="ltag__tag ltag__tag__id__5339"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/kentico" class="ltag__tag__link"&gt;kentico&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag__tag ltag__tag__id__57801"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/xperience" class="ltag__tag__link"&gt;xperience&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Or my &lt;a href="https://dev.to/seangwright/series"&gt;Kentico Xperience blog series&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8185"&gt;Kentico Xperience Xplorations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/9483"&gt;Kentico Xperience MVC Widget Experiments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/seangwright/series/8740"&gt;Bits of Xperience&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>xperience</category>
      <category>kentico</category>
      <category>aspnetcore</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
