<?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: Michael Eustace</title>
    <description>The latest articles on Forem by Michael Eustace (@michael419).</description>
    <link>https://forem.com/michael419</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%2F419563%2F1a80cea0-1514-4b0d-a270-accfdc40a457.jpg</url>
      <title>Forem: Michael Eustace</title>
      <link>https://forem.com/michael419</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/michael419"/>
    <language>en</language>
    <item>
      <title>Get to know Xperience by Kentico: Unlocking performance insights with MiniProfiler</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Wed, 26 Jun 2024 16:19:47 +0000</pubDate>
      <link>https://forem.com/michael419/get-to-know-xperience-by-kentico-unlocking-performance-insights-with-miniprofiler-2b2c</link>
      <guid>https://forem.com/michael419/get-to-know-xperience-by-kentico-unlocking-performance-insights-with-miniprofiler-2b2c</guid>
      <description>&lt;p&gt;In today's fast-paced digital world, performance is a critical factor in ensuring a seamless user experience, and as developers, we constantly seek tools that help us optimize and monitor the performance of our applications.&lt;/p&gt;

&lt;p&gt;One such powerful tool is MiniProfiler, and when integrated with Xperience by Kentico, it offers invaluable insights into your application's performance.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore the benefits of this integration and how you can set it up to elevate your web development projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  📟 What is MiniProfiler?
&lt;/h2&gt;

&lt;p&gt;MiniProfiler is a simple but effective tool for profiling and monitoring the performance of your web applications. It provides detailed insights into the performance of your code, database queries, and HTTP requests, helping you identify bottlenecks and optimize your application's performance.&lt;/p&gt;

&lt;p&gt;I've personally been using it for over a decade, and I find myself coming back to it again-and-again because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's just so easy to set up and configure&lt;/li&gt;
&lt;li&gt;It helps you identify the most evil thing that can slow down your application...SQL queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ✅ Kentico has an integration for MiniProfiler
&lt;/h2&gt;

&lt;p&gt;Kentico's dev team, working on the Xperience by Kentico product, were using MiniProfiler internally, so they decided to open-source the integration earlier this year so that developers, like us, can use it. &lt;/p&gt;

&lt;p&gt;Thanks Kentico dev team 🙌&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 How do I integrate MiniProfiler into my Xperience by Kentico web application?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the &lt;code&gt;Kentico.Xperience.MiniProfiler&lt;/code&gt; nuget package into your XbyK project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open your &lt;code&gt;Program.cs&lt;/code&gt; file and add the following code:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Kentico.Xperience.MiniProfiler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&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="n"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Kentico builder services configuration ...&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;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// After the call to builder.Services.AddKentico( ... );&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&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;AddKenticoMiniProfiler&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&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;UseKentico&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// After call to UseKentico()&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&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;UseMiniProfiler&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;Underneath-the-hood, this code sets up and configures MiniProfiler's middleware, along with the script that needs adding to the HTML &lt;code&gt;body&lt;/code&gt;&lt;br&gt;
tag that renders the profiler UI when you load a page.&lt;/p&gt;
&lt;h2&gt;
  
  
  🏃 You're all set
&lt;/h2&gt;

&lt;p&gt;Run your local website and you will see the profiler display its output in the top-left of the page. &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%2Fqu6mmiv9q295xv8xyoj3.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%2Fqu6mmiv9q295xv8xyoj3.png" alt="MiniProfiler UI" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you are seeing here is, as &lt;a href="https://miniprofiler.com/dotnet/HowTo/ProfileCode" rel="noopener noreferrer"&gt;MiniProfiler themselves&lt;/a&gt; put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MiniProfiler is generally setup as 1 profiler per “action” (e.g. an HTTP Request, or startup, or some job) of an application. Inside that profiler, there are steps. Inside steps, you can also have custom timings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, a list of profilers in the top-left of the screen is displayed. Click one to reveal a list of the steps that were executed as part of that profiler. Each step displays the total duration taken to execute the step.&lt;/p&gt;

&lt;p&gt;If any SQL queries were executed as part of the step, then they are display as blue links. Click one and a modal popup displays the SQL query statements that were performed, which enables you to start thinking about how you might optimise your the code that creates the query. Great stuff 🙌&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%2F1213xw5uvtg9gn76swqf.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%2F1213xw5uvtg9gn76swqf.png" alt="MiniProfiler SQL " width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  ⏱️ Adding custom timing
&lt;/h2&gt;

&lt;p&gt;This is where things get really interesting. A pretty common use case is that you might want to time a specific piece of code, and any resulting SQL queries executed as part of that code.&lt;/p&gt;

&lt;p&gt;Say you have a widget and you want to check the duration of a call to a repository method from the widget's &lt;code&gt;ViewComponent&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;1. Add the MiniProfiler namespace in your using directives in the &lt;code&gt;ViewComponent&lt;/code&gt; of your widget:&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;using&lt;/span&gt; &lt;span class="nn"&gt;StackExchange.Profiling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Then wrap your code with MiniProfiler's &lt;code&gt;Step&lt;/code&gt; extension method, giving the step a custom name:&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;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Coffee&lt;/span&gt;&lt;span class="p"&gt;&amp;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;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;Coffee&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MiniProfiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Custom: CoffeeRepository.GetCoffees()"&lt;/span&gt;&lt;span class="p"&gt;))&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;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCoffees&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;3. Now build and run the site again, navigate to a page that has that widget placed on it, expand the relevant profiler:&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%2Fdhrr8ldbuvjmgkm9ypg6.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%2Fdhrr8ldbuvjmgkm9ypg6.png" alt="MiniProfiler custom step" width="600" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is really powerful stuff as it enables you to measure the performance of your code, quickly and easily, then it's up to you to hone your code to increase performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Ensuring you have caching disabled
&lt;/h2&gt;

&lt;p&gt;In my example screenshot above, I'm performing these web page loads straight after having built my Dancing Goat sample project, so there won't be any cached data at this point. This means I will see all of the SQL calls profiled as they haven't been cached at this point. &lt;/p&gt;

&lt;p&gt;Subsequent reloads of the same page will display reduced list of steps as the resultant data returned from SQL queries are cached by the system, and so the MiniProfiler UI won't show them all.&lt;/p&gt;

&lt;p&gt;Kentico's Dancing Goat Xperience by Kentico sample site follows their best practice for &lt;a href="https://docs.kentico.com/developers-and-admins/development/caching/data-caching" rel="noopener noreferrer"&gt;data caching&lt;/a&gt; and &lt;a href="https://docs.kentico.com/developers-and-admins/development/caching/output-caching" rel="noopener noreferrer"&gt;output caching&lt;/a&gt; where the explicitly recommend disabling caching for pages when view in preview mode, so if your project has followed this approach, then you can always load the page in the preview tab when authoring the page in your website channel, and you'll see the MiniProfiler UI.&lt;/p&gt;

&lt;p&gt;Alternatively, you might have architected your caching approach to utilise an app settings key to disabled/enable caching at a global level.&lt;/p&gt;

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

&lt;p&gt;Integrating MiniProfiler with Xperience by Kentico is a powerful way to gain deep insights into the performance of your web applications. By following the steps outlined above, you can set up MiniProfiler and start optimizing your application's performance, ensuring a smooth and efficient user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Kentico/xperience-by-kentico-miniprofiler" rel="noopener noreferrer"&gt;Xperience by Kentico MiniProfiler project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://miniprofiler.com/dotnet/HowTo/ProfileCode" rel="noopener noreferrer"&gt;MiniProfiler: How-To Profile Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kentico</category>
      <category>xperience</category>
      <category>miniprofiler</category>
    </item>
    <item>
      <title>Get to know Xperience by Kentico: The Page Selector form component</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Tue, 18 Jun 2024 14:19:59 +0000</pubDate>
      <link>https://forem.com/michael419/get-to-know-xperience-by-kentico-the-page-selector-form-component-59a2</link>
      <guid>https://forem.com/michael419/get-to-know-xperience-by-kentico-the-page-selector-form-component-59a2</guid>
      <description>&lt;p&gt;Form Components are a great way of providing some pretty powerful content editing functionality to marketers and editors in Xperience by Kentico.&lt;/p&gt;

&lt;p&gt;They enable us web developers to create fields in editing dialogues within the admin UI that, in-turn, provide functionality to marketers for them to enter and select data to display on their website.&lt;/p&gt;

&lt;p&gt;This is achieved by programmatically assigning form components to the properties of widgets, sections, page templates, and much more, by using &lt;code&gt;Form component attributes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Xperience by Kentico provides a whole host of form components out-of-the-box, and this is really powerful as it gives us web developers the flexibility to provide different content editing experiences to suit the needs of marketers, who these days, are looking to content manage their marketing channels faster, and more effectively, than ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Page Selector?
&lt;/h2&gt;

&lt;p&gt;One of my favourite form components is the Page Selector, which enables you to provide a method for editors to select pages from the site tree in a website channel.&lt;/p&gt;

&lt;p&gt;As a developer, you can then work with the data of those selected pages to provide functionality in custom components. &lt;/p&gt;

&lt;h2&gt;
  
  
  A trip down memory lane...
&lt;/h2&gt;

&lt;p&gt;The Page Selector has been through a number of iterations in-line with Kentico's development of their DXP. Each time, they've added to its capabilities to make the feature more-and-more useful...&lt;/p&gt;

&lt;h3&gt;
  
  
  Kentico Xperience 12
&lt;/h3&gt;

&lt;p&gt;The page selector started its journey in Kentico Xperience 12, where it was introduced as a straightforward method for enabling editors to select a single page from the 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%2Fjo2qmuanrdwsg1dabqe2.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%2Fjo2qmuanrdwsg1dabqe2.png" alt="The Page Selector form component in Kentico Xperience 12" width="800" height="453"&gt;&lt;/a&gt;&lt;em&gt;Kentico Experience 12 Page Selector&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Under this guise, the component returned the &lt;code&gt;NodeGuid&lt;/code&gt; of the selected page. Its configuration was fairly limited, but it did have a &lt;code&gt;RootPath&lt;/code&gt; property that allowed you to limit the selection of pages to those that were located in a specific section of the content tree, otherwise the entire tree was available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kentico Xperience 13
&lt;/h3&gt;

&lt;p&gt;In Kentico Xperience 13, Kentico added the ability for marketers to select multiple pages. In addition, they added the &lt;code&gt;MaximumPages&lt;/code&gt; property that enabled developers to limit the number of pages that could be selected.&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%2Fne71yawfut35fb2a9j2d.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%2Fne71yawfut35fb2a9j2d.png" alt="The Page Selector form component in Kentico Xperience 13" width="800" height="454"&gt;&lt;/a&gt;&lt;em&gt;Kentico Xperience 13 Page Selector&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This was a great improvement for editing efficency. Imagine you were in the scenario where marketers had to select three pages to populate a widget with call-to-action links, you could now have one form component to select all three pages to link to, instead of having three separate form components, thus reducing clutter in the widget dialog box, and producing a much more streamlined experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xperience by Kentico
&lt;/h3&gt;

&lt;p&gt;In Xperience by Kentico, the form component was rebuilt from the ground up to work with Kentico's newly redesigned admin UI, which is built in React, sitting on .Net.&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%2F2g2hkb5rvifzf7xw9cfr.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%2F2g2hkb5rvifzf7xw9cfr.png" alt="The Page Selector form component in Xpereince by Kentico" width="800" height="572"&gt;&lt;/a&gt;&lt;em&gt;Xperience by Kentico Page Selector&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The thing I really like though is that they took the opportunity to add the killer feature of making selected pages sortable by the marketer.&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%2F883hs53nej0csjschdvx.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%2F883hs53nej0csjschdvx.png" alt="The Page Selector form component in Xperience by Kentico" width="731" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With KX13, you had to select the desired pages in the order in which you wanted them to be displayed. And if you wanted to change the order, you had to deselect all pages, and then reselect them in the order.&lt;/p&gt;

&lt;p&gt;Now with Xperience by Kentico, that issue has disappeared, with the addition of the &lt;code&gt;Sortable&lt;/code&gt; property, which when set to &lt;code&gt;true&lt;/code&gt;, enables the drag-and-drop handles, illustrated in the above screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical example
&lt;/h2&gt;

&lt;p&gt;Imagine you are building an Article Cards widget for the page builder, and you'd like marketers to be able to select the articles they want to display in the widget.&lt;/p&gt;

&lt;p&gt;Assuming you have content modelled your articles to be pages in the content tree, you can create a property for your widget, and utilise the &lt;code&gt;WebPageSelectorComponent&lt;/code&gt;, which is the form component attribute you need to use when you want to render the Page Selector for a widget property:&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;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;CMS.Websites&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Kentico.PageBuilder.Web.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Kentico.Xperience.Admin.Websites.FormAnnotations&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;CardWidgetProperties&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IWidgetProperties&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;WebPageSelectorComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TreePath&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/Articles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;MaximumPages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;Sortable&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;ItemModifierType&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;WebPagesWithUrlWebPagePanelItemModifier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="n"&gt;Label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Select pages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&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;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebPageRelatedItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SelectedArticlePages&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="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;WebPageRelatedItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, I've added the following configuration properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TreePath&lt;/code&gt; - so that it only offers up pages that sit within our Article section of the tree. We want to lead marketers to the correct section of the content tree to make their life easier. It wouldn't be a great experience if we just displayed the entire content tree, otherwise as the tree grows in size, it will make it more difficult for them to find the correct section in the tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MaximumPages&lt;/code&gt; - we've ensured marketers can only select a maximum of 3 pages, as (hypothetically) in this case the design and solution dictate a maximum limit of article cards to be displayed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Sortable&lt;/code&gt; - as we'd like marketers to be to control the sort order of the article cards separate for each instance of the widget.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ItemModifierType&lt;/code&gt; - lets us set a type that implements the &lt;code&gt;IWebPagePanelItemModifier&lt;/code&gt; interface, and in this case, we are using the built-in &lt;code&gt;WebPagesWithUrlWebPagePanelItemModifier&lt;/code&gt; type, which ensure that only pages that have the URL feature enabled are selectable in the content tree. Content-only pages are still displayed, but in a disabled state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course, there are the generic &lt;code&gt;Label&lt;/code&gt; and &lt;code&gt;Order&lt;/code&gt; properties for defining the text for the property's label, and the order in which it appears in the widget dialog in relation to other properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  In conclusion
&lt;/h2&gt;

&lt;p&gt;The Page Selector is now a very useful, mature tool in the Kentico web developer's arsenal that helps to streamline the editing process for marketers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;a href="https://docs.kentico.com/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/reference-admin-ui-form-components#page-selector" rel="noopener noreferrer"&gt;Kentico's Page Selector documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;View &lt;a href="https://docs.kentico.com/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/reference-admin-ui-form-components" rel="noopener noreferrer"&gt;Kentico extensive list of Admin UI form components&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kentico</category>
      <category>xperience</category>
    </item>
    <item>
      <title>Xperience by Kentico: 5 useful developer resources for getting started with XbyK</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Fri, 31 May 2024 14:13:54 +0000</pubDate>
      <link>https://forem.com/michael419/xperience-by-kentico-5-useful-developer-resources-for-getting-started-with-xbyk-35m0</link>
      <guid>https://forem.com/michael419/xperience-by-kentico-5-useful-developer-resources-for-getting-started-with-xbyk-35m0</guid>
      <description>&lt;p&gt;Have you tried Xperience by Kentico yet? 🤔 &lt;/p&gt;

&lt;p&gt;If not, it’s this easy to get started. 🙌 Here’s 5 useful resources to get you going:&lt;/p&gt;

&lt;h2&gt;
  
  
  📖 1. Read this blog post on the Kentico Community forum
&lt;/h2&gt;

&lt;p&gt;It that will inform you where the documentation lives, how to access tutorials and quick-start guides &lt;a href="https://community.kentico.com/blog/learning-xperience-by-kentico-as-a-software-developer" rel="noopener noreferrer"&gt;https://community.kentico.com/blog/learning-xperience-by-kentico-as-a-software-developer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ✉ 2. Sign-up to the Kentico Community Portal newsletter
&lt;/h2&gt;

&lt;p&gt;As a developer, it’s one of the best methods for staying informed on the latest releases, useful articles and guides &lt;a href="https://community.kentico.com/newsletter-signup" rel="noopener noreferrer"&gt;https://community.kentico.com/newsletter-signup&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📽 3. Watch these brilliant technical spotlight videos by Kentico’s Lead Product Evangelist, &lt;a class="mentioned-user" href="https://dev.to/seangwright"&gt;@seangwright&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We used these recently at our in-house agency hackathon to get developers, who were new to Xperience by Kentico, up-to-speed in minutes with setting up local environments (on Windows/Mac/Linux), creating content types, page templates, widgets, and using continuous integration &lt;a href="https://www.youtube.com/playlist?list=PL9RdJplq_ukaIt4_V4GAbeJ_qk1AKuFrP" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PL9RdJplq_ukaIt4_V4GAbeJ_qk1AKuFrP&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📄 4. Get to know the developer documentation
&lt;/h2&gt;

&lt;p&gt;Kentico’s documentation is second-to-none and it’s one of the many reasons why I’ve been able to successfully build and deliver solutions for our clients, quickly and efficiently, as every aspect of the product is immaculately documented in a format that is easy to read, understand, and action. &lt;a href="https://docs.kentico.com/developers-and-admins" rel="noopener noreferrer"&gt;https://docs.kentico.com/developers-and-admins&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📢 5. If you get stuck, you’re not alone...
&lt;/h2&gt;

&lt;p&gt;Ask a question on the Kentico Community Portal Q&amp;amp;A forum. Other developers and Kentico MVPs, even Kentico employees, will be on-hand to help &lt;a href="https://community.kentico.com/q-and-a" rel="noopener noreferrer"&gt;https://community.kentico.com/q-and-a&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Become Kentico Developer Certified?&lt;/p&gt;

&lt;p&gt;Once you're up-and-running, and have accrued some experience with...Xperience, why not consider becoming a Kentico Certified Developer? The Xperience by Kentico Certified Developer exam is not currently available, but will be released sometime mid-2024, but in the meantime, you can draw some learnings for the equivalent Kentico Xperience 13 developer exam in my post: &lt;a href="https://dev.to/michael419/kentico-xperience-13-5-tips-on-how-to-ace-the-kentico-certified-developer-exam-3dkh"&gt;5 tips on how to ace the Kentico Certified Developer exam&lt;/a&gt; 😉&lt;/p&gt;

&lt;p&gt;Good luck 👍&lt;/p&gt;

</description>
      <category>kentico</category>
      <category>xperience</category>
      <category>cms</category>
    </item>
    <item>
      <title>Kentico Xperience 13: 5 tips on how to ace the Kentico Certified Developer exam</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Mon, 19 Feb 2024 00:06:13 +0000</pubDate>
      <link>https://forem.com/michael419/kentico-xperience-13-5-tips-on-how-to-ace-the-kentico-certified-developer-exam-3dkh</link>
      <guid>https://forem.com/michael419/kentico-xperience-13-5-tips-on-how-to-ace-the-kentico-certified-developer-exam-3dkh</guid>
      <description>&lt;p&gt;I've been building websites using Kentico for nearly a decade, and during that time, I've witnessed the product go from strength-to-strength, evolving into the fantastic DXP that it is today.&lt;/p&gt;

&lt;p&gt;But with an ever improving product, comes new features, new development models and, therefore, the need to keep abreast of these developments so that, as a developer, you can get the most from the product.&lt;/p&gt;

&lt;p&gt;And of course, there’s no better way to showcase your knowledge of Kentico to the world, than by completing the Certified Developer exam.&lt;/p&gt;

&lt;p&gt;Having sat the exam three times previously (you need to re-sit the exam every two years to remain certified), last month signalled the end of my (then) current certification, so it was time to prep' to take the exam, once again.&lt;/p&gt;

&lt;p&gt;So, herein lies a list of my top-tips to help you enter the exam with excitement, confidence, and hopefully a few less nerves.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Know the format 📋
&lt;/h2&gt;

&lt;p&gt;The exam format has largely been the same over the years, but at this time of writing, the current format is as follows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's web-based&lt;/li&gt;
&lt;li&gt;You have 90 minutes to complete 50 questions.&lt;/li&gt;
&lt;li&gt;The exam questions consist of single choice answers.&lt;/li&gt;
&lt;li&gt;You can navigate forward and back through your answers, meaning if you pass on a question, you can always go back to it later if you have time left over&lt;/li&gt;
&lt;li&gt;The pass mark is 70%, meaning you can incorrectly answer 15 questions and still pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if we're talking exam-tactics, you're probably thinking, "50 questions in 90 minutes, that's 1min 48secs I should try to complete each question in."&lt;/p&gt;

&lt;p&gt;Well yes...and no.&lt;/p&gt;

&lt;p&gt;Yes it's good to stay disciplined and attempt to answer questions in a timely fashion, but the cold-hard-truth is that you might well get stuck on a question (or two, or three), and spend longer than that searching for the answer. The trick is not to let this panic you, as conversely, there will be questions that you will know the answer to straight away, so it'll all balance itself out.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The &lt;a href="https://docs.kentico.com/13" rel="noopener noreferrer"&gt;Kentico Xperience 13 documentation&lt;/a&gt; is your friend 💼
&lt;/h2&gt;

&lt;p&gt;Remember, this is an open-book exam, you don't need to be a complete expert in Kentico. This is as much a test on how efficent you are at searching for solutions in the documentation, as it is your knowledge of the product - kinda like the real world.&lt;/p&gt;

&lt;p&gt;Take time to read and familiarise yourself with the Kentico docs, its layout and search functionality, so that when you are hit with that tricky question, you already know &lt;u&gt;how&lt;/u&gt; to search and find the information you need to answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Know the product 🧠
&lt;/h2&gt;

&lt;p&gt;The exam is not something you can just sit without any prior real-world experience of developing a website using Kentico. &lt;/p&gt;

&lt;p&gt;I often say to devs that you need a couple of builds under-your-belt before attempting the exam, and then it's a case of identifying which areas of Kentico you are not familiar with, and brushing up on them.&lt;/p&gt;

&lt;p&gt;I often find that developers who are newer to Kentico will be familiar with features such as...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page builder&lt;/li&gt;
&lt;li&gt;Creating page templates&lt;/li&gt;
&lt;li&gt;Creating widgets&lt;/li&gt;
&lt;li&gt;Creating page types&lt;/li&gt;
&lt;li&gt;Displaying content on live site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...but not so much in areas like...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;K# and macro syntax&lt;/li&gt;
&lt;li&gt;Content staging&lt;/li&gt;
&lt;li&gt;Configuring Kentico in a team environment&lt;/li&gt;
&lt;li&gt;Continuous integration&lt;/li&gt;
&lt;li&gt;and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, the exam tests your knowledge of all areas of Kentico. No stone is left unturned, so make sure you read the Kentico preparation guide (referenced at the bottom of this article).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Take the sample exam ASAP 📝
&lt;/h2&gt;

&lt;p&gt;Once you've purchased the exam voucher, you will be given access to a shorter sample exam. Consisting of 11 questions, the sample exam is presented in the format of the actual exam.&lt;/p&gt;

&lt;p&gt;This presents you with a great opportunity to get a feel for the exam, the questions, format, and to benchmark your current knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Take the day to revise 📆
&lt;/h2&gt;

&lt;p&gt;On the day of the exam, take the day to revise, read through your notes, whatever you feel works best to give you best chance to pass the exam, then take the exam when you are ready.&lt;/p&gt;

&lt;p&gt;If you don't pass, not to worry, you can resit the exam after 24 hours. Take stock and try and think about the area where you need to improve upon before re-sitting.&lt;/p&gt;

&lt;p&gt;✅ Once you passed the exam though, enjoy the moment, and the rush, tell all your friends, and update your LinkedIn profile with your new achievement. 📢&lt;/p&gt;

&lt;p&gt;Good luck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the Kentico's &lt;a href="https://res.cloudinary.com/xperience-io/image/upload/v1641818516/Training/Exams/KenticoXperienceCertifiedDeveloperPreparationGuide_v13_a2hjtg.pdf" rel="noopener noreferrer"&gt;Developer Certification preparation guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Read more about the &lt;a href="https://www.kentico.com/services/certification/xperience-developer-certification" rel="noopener noreferrer"&gt;Kentico Certified Developer exam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Learn about &lt;a href="https://www.kentico.com/services/training#developers" rel="noopener noreferrer"&gt;training courses available for Kentico developers&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kentico</category>
      <category>xperience</category>
      <category>certification</category>
    </item>
    <item>
      <title>How to generate a trusted local IIS certificate for .Net development</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Thu, 31 Aug 2023 16:07:28 +0000</pubDate>
      <link>https://forem.com/michael419/how-to-generate-a-trusted-local-iis-certificate-for-net-development-56gn</link>
      <guid>https://forem.com/michael419/how-to-generate-a-trusted-local-iis-certificate-for-net-development-56gn</guid>
      <description>&lt;p&gt;As a .Net developer, I often run .Net websites in a local IIS instance on my developer machine, and it can be useful to be able to serve the site over HTTPS.&lt;/p&gt;

&lt;p&gt;To create a trusted, local, self-signed SSL certificate, follow these steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Generate the certificate
&lt;/h2&gt;

&lt;p&gt;Open Powershell and enter the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-SelfSignedCertificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mysite.local"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cert:\LocalMachine\My"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Trust the certificate
&lt;/h2&gt;

&lt;p&gt;We now need to ensure the certificate is trusted on the local machine, otherwise you'll get a certificate warning when you navigate to your local website over HTTPS in a browser.&lt;/p&gt;

&lt;p&gt;To do this in Windows 10:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start -&amp;gt; Run &lt;code&gt;certlm.msc&lt;/code&gt; to open the Certificate Manager tool&lt;/li&gt;
&lt;li&gt;Expand the &lt;code&gt;Intermediate Certification Authorities&lt;/code&gt; folder in the left window of the tool&lt;/li&gt;
&lt;li&gt;Click the &lt;code&gt;Certificates&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Find the certificate you just generated in the right-hand window&lt;/li&gt;
&lt;li&gt;Click and drag it over the &lt;code&gt;Trusted Root Certification Authorities&lt;/code&gt; folder in the left window - this will set the certificate as trusted on your local machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Update IIS to use the new certificate
&lt;/h2&gt;

&lt;p&gt;Now you can create/update the HTTPS binding for your local website in IIS to use the new certificate - you should see the new certificate listed in the &lt;code&gt;SSL certificate:&lt;/code&gt; drop-down list.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>iis</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JetBrains Rider: How to stop and start an IIS app pool on solution build</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Wed, 23 Aug 2023 16:48:16 +0000</pubDate>
      <link>https://forem.com/michael419/jetbrains-rider-how-to-stop-and-start-an-iis-app-pool-on-solution-build-3b2b</link>
      <guid>https://forem.com/michael419/jetbrains-rider-how-to-stop-and-start-an-iis-app-pool-on-solution-build-3b2b</guid>
      <description>&lt;p&gt;I recently made the switch from Visual Studio to JetBrains Rider for my .NET Core web application projects.&lt;/p&gt;

&lt;p&gt;Whilst I am very pleased with the new development experience, there was one issue that I had to overcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When building a solution where I was running the web application in a locally hosted IIS instance, the build often failed because of the error below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Microsoft.Common.CurrentVersion.targets(4685, 5): [MSB3027] Could not copy "obj\Debug\net7.0\foo.dll" to "bin\Debug\net7.0\foo.dll". Exceeded retry count of 10. Failed. The file is locked by: "IIS Worker Process (10592)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This was because the &lt;code&gt;IIS Worker Process&lt;/code&gt; had locked the DLL files in the &lt;code&gt;bin&lt;/code&gt; directory, causing the build to fail when it attempted to overwrite them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;The solution is to stop the application pool for the website in IIS to release the lock, perform the build, then start the app pool again.&lt;/p&gt;

&lt;p&gt;If you had configured Visual Studio to run using the local IIS website, then it would take care of stopping and starting the app pool for you, but JetBrains Rider doesn't do this.&lt;/p&gt;

&lt;p&gt;The work around is to create two powershell scripts to stop and start the app pool, then configure pre and post build events when your web project build is triggered.&lt;/p&gt;

&lt;p&gt;Here is the code for the two PowerShell scripts. Copy and paste the code into two files and store them in the root of the web project in Rider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StopApplicationPool.ps1&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;app_pool_name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WebAppPoolState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Started"&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopping application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Stop-WebAppPool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Wait loop to check the status of the application pool&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;do&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Waiting for application pool to stop..."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Start-Sleep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Seconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# Wait for 1 seconds before checking again&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WebAppPoolState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&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="kr"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Started"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopping"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; has been stopped."&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="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopping"&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="c"&gt;# Wait loop to check the status of the application pool&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;do&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Waiting for application pool to stop..."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Start-Sleep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Seconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# Wait for 1 seconds before checking again&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WebAppPoolState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&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="kr"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopping"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; has been stopped."&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="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopped"&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; is already stopped."&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="kr"&gt;else&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; is in an unknown state: &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="s2"&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;Notice the script above polls the app pool status until it's actually reporting the status as &lt;code&gt;Stopped&lt;/code&gt;, before exiting to allow the build to begin. Remember, application pools don't stop immediately, they shutdown gracefully to allow any processes that were in-progress, at the point when the &lt;code&gt;stop&lt;/code&gt; command was issued, to complete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StartApplicationPool.ps1&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;app_pool_name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-WebAppPoolState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stopped"&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starting application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Start-WebAppPool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; has been started."&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="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Started"&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; is already started."&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="kr"&gt;else&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="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Application pool &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolName&lt;/span&gt;&lt;span class="s2"&gt; is in an unknown state: &lt;/span&gt;&lt;span class="nv"&gt;$AppPoolStatus&lt;/span&gt;&lt;span class="s2"&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;&lt;strong&gt;Configure the pre and post build events&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pre build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StopApplicationPool.ps1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Post build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StartApplicationPool.ps1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Rider, open the Properties for the project and copy and paste the above into the fields highlighted below.&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%2Fnyspobbqt0za11j32szy.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%2Fnyspobbqt0za11j32szy.png" alt="Executing commands before and after build events in JetBrains Rider" width="703" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Happy building 😀&lt;/p&gt;

</description>
      <category>aspdotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Kentico Xperience 13: How to programmatically update the URL slug of a page</title>
      <dc:creator>Michael Eustace</dc:creator>
      <pubDate>Sun, 04 Jul 2021 15:42:43 +0000</pubDate>
      <link>https://forem.com/michael419/kentico13-how-to-programmatically-update-the-url-slug-of-a-page-3fii</link>
      <guid>https://forem.com/michael419/kentico13-how-to-programmatically-update-the-url-slug-of-a-page-3fii</guid>
      <description>&lt;p&gt;I recently wrote a console app to migrate data from Drupal to Kentico Xperience 13 using the Xperience APIs.&lt;/p&gt;

&lt;p&gt;As part of this, I was required to recreate pages in Xperience so that they had the same URL as their counterpart in Drupal.&lt;/p&gt;

&lt;p&gt;My approach was to, for each page in my Drupal data source, create a new instance of the &lt;code&gt;TreeNode&lt;/code&gt; object and set its values based on the values stored for that page in Drupal.&lt;/p&gt;

&lt;p&gt;I also set the &lt;code&gt;NodeAlias&lt;/code&gt; value of the &lt;code&gt;TreeNode&lt;/code&gt; object at this point so that it matched the page's "page alias" (aka slug) in Drupal.&lt;/p&gt;

&lt;p&gt;I then called Insert() on my object instance to create the page in Xperience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page URLs work differently in Xperience 13
&lt;/h2&gt;

&lt;p&gt;Now in the old days of Kentico CMS, this would have been good enough as each page's URL was based off of its &lt;code&gt;NodeAliasPath&lt;/code&gt; but in Xperience 13 the URL of a page is controlled separately and is managed by an editor via the URLs tab in Kentico.&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%2Fz2qgb0hig0mh0rlyqic6.JPG" 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%2Fz2qgb0hig0mh0rlyqic6.JPG" alt="Updating URL slug in Kentico Xperience 13" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I found that when I called &lt;code&gt;Insert()&lt;/code&gt;, the slug was being auto generated based on the &lt;code&gt;DocumentName&lt;/code&gt; of the page, rather than just copying the &lt;code&gt;NodeAlias&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I now needed a way to update the slug.&lt;/p&gt;

&lt;h2&gt;
  
  
  In search of a solution for updating the slug
&lt;/h2&gt;

&lt;p&gt;Off I went in search of slugs (that's the first time I've ever said that...I promise) and I found they were stored in the &lt;code&gt;PageUrlPathUrlPath&lt;/code&gt; column (try and say that 10 times in quick succession) within the &lt;code&gt;CMS_PageUrlPath&lt;/code&gt; database table with a foreign key to the &lt;code&gt;NodeID&lt;/code&gt; of the page in question.&lt;/p&gt;

&lt;p&gt;My immediate thought was that I could retrieve the relevant &lt;code&gt;PageUrlPathInfo&lt;/code&gt; object, update the slug et voilà! But no luck.&lt;/p&gt;

&lt;p&gt;It's then that I noticed there is a hash stored against each &lt;code&gt;PageUrlPath&lt;/code&gt; record and that this must be updated in conjunction with updating the slug.&lt;/p&gt;

&lt;p&gt;Usually when you see a hash against a record in Kentico, it means there's some helper method somewhere which you can use to update the record and manage the hash at the same time. It's just a case of finding this helper method.&lt;/p&gt;

&lt;p&gt;Having exhausted my friends Google and Kentico devnet, I guessed that the underlying aspx page which is providing the functionality for editors to update the slug in the URLs tab must be using such a method.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to update the URL slug
&lt;/h2&gt;

&lt;p&gt;Having found said aspx page in the CMS Visual Studio project &lt;code&gt;CMSModules/Content/CMSDesk/Properties/Urls.aspx&lt;/code&gt; I followed the code through and found the &lt;code&gt;PageUrlPathSlugUpdater&lt;/code&gt; which does exactly the job I needed.&lt;/p&gt;

&lt;p&gt;Here's the code to update the slug:&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;PageUrlPathSlugUpdater&lt;/span&gt; &lt;span class="n"&gt;pageUrlPathSlugUpdater&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;PageUrlPathSlugUpdater&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;Culture&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CollisionData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collidingPaths&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;pageUrlPathSlugUpdater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newSlug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;collidingPaths&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;collidingPaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// Slug was updated&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// Slug wasn't updated&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pros and Cons of separating page URL from NodeAliasPath
&lt;/h2&gt;

&lt;p&gt;One of the limitations of the &lt;code&gt;NodeAlias&lt;/code&gt; property is that there is a 70 character max limit. This was a problem as some of the page aliases for pages in Drupal were longer than this so they were being truncated. &lt;/p&gt;

&lt;p&gt;Luckily, there is no such limit on URL slugs, so I was able to replicate the URLs from Drupal into Kentico without issue.&lt;/p&gt;

&lt;p&gt;One disadvantage now is that when you retrieve the data for a page in code, you now need to, in addition, use &lt;code&gt;PageUrlRetriever.Retrieve(nodeAliasPath)&lt;/code&gt; to get the URL of the page, which generates another (if not more) calls to the database. Previously, you just needed to ensure you were retrieving the NodeAliasPath when getting the page data. &lt;/p&gt;

&lt;p&gt;There's no way to retrieve URLs for pages in bulk either, so you can imagine in code that creates, say, website navigation where you are in a foreach loop scenario, some developers may end up calling this method for each iteration and, subsequently, generating a tonne of SQL queries.&lt;/p&gt;

&lt;p&gt;Luckily there is a way to get around this. More on that in my next post.&lt;/p&gt;

</description>
      <category>kentico</category>
      <category>kentico13</category>
      <category>xperience</category>
    </item>
  </channel>
</rss>
