Blog posts by Elucid Labs2024-03-12T21:56:00.0000000Z/blogs/elucid-labs/Optimizely WorldMoving IIS URL Rewrite Rules to .NET Core: A Practical Guidehttps://blog.paulmcgann.dev/2024/03/moving-iis-url-rewrite-rules-to-net.html2024-03-12T21:56:00.0000000Z<br />In the fast-paced world of web development, staying up-to-date with the latest technologies is crucial. As businesses evolve, so do their digital needs. One common challenge faced by developers is migrating URL rewrite rules from IIS (Internet Information Services) to .NET Core. If you're embarking on this journey, fear not! This guide will walk you through the process in a clear and practical manner.<br /><br /><b>Understanding the Basics</b><br /><br />URL rewriting plays a crucial role in managing web traffic and optimizing user experience. It involves intercepting incoming requests and redirecting them to different URLs based on predefined rules. In the IIS environment, these rules are typically configured in the web.config file. However, with .NET Core, URL rewriting is handled differently, using middleware within the application itself.<br /><br /><b>Step 1: Assess Your Current Setup</b><br /><br />Before diving into migration, it's essential to understand your existing URL rewrite rules configured in the web.config file. Take stock of each rule, noting down its pattern and rewrite destination.<br /><br /><b>Step 2: Install the Microsoft.AspNetCore.Rewrite Package</b><br /><br />To handle URL rewriting in .NET Core, you'll need to install the Microsoft.AspNetCore.Rewrite package. This can be done easily using the .NET CLI or NuGet Package Manager in Visual Studio.<br /><pre><code class="bash">
dotnet add package Microsoft.AspNetCore.Rewrite
</code></pre>
<br /><b>Step 3: Configure URL Rewriting Middleware</b><br /><br />Once the package is installed, you'll configure the URL rewriting middleware in the Startup.cs file of your .NET Core application.<br />
<pre><code class="csharp">
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Rewrite;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Add any necessary services here
}
public void Configure(IApplicationBuilder app)
{
// Other middleware configurations
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddIISUrlRewrite(iisUrlRewriteStreamReader);
app.UseRewriter(options);
}
// Additional middleware configurations
}
}
</code></pre>
<br /><b>Step 4: Migrate URL Rewrite Rules</b><br /><br />Now, it's time to migrate your existing URL rewrite rules from the web.config file to the IISUrlRewrite.xml file. You can keep the exisiting rewrite element with all your current iis rewrite rules in place, see the below.<div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEikduDTh1GlrFsDUVcYXekicOfjHh3HdnZeZ5-emlChsyEjj3NXMffkK9kyqLHJiRrnEv7cII7Br5yj-GPCdND45Wt46NqOoOX0PRU8hyfPMzqyGK1ly-lIUOa2wiIqFSIAZiCkSvRcWf-sp6i6xSXbiWCdyPhxv4taqpupQ8KQyQ4lhkC0qPhj-mx9oyE" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="502" data-original-width="1857" height="157" src="https://blogger.googleusercontent.com/img/a/AVvXsEikduDTh1GlrFsDUVcYXekicOfjHh3HdnZeZ5-emlChsyEjj3NXMffkK9kyqLHJiRrnEv7cII7Br5yj-GPCdND45Wt46NqOoOX0PRU8hyfPMzqyGK1ly-lIUOa2wiIqFSIAZiCkSvRcWf-sp6i6xSXbiWCdyPhxv4taqpupQ8KQyQ4lhkC0qPhj-mx9oyE=w578-h157" width="578" /></a></div><br /><br />
<br /><b>Step 5: Test and Iterate</b><br /><br />After migrating your rules, thorough testing is essential. Verify that all URLs are being rewritten / redirected correctly without any unexpected redirects or errors.<br /><br /><b>Conclusion</b><br /><br />Migrating URL rewrite rules from IIS web.config to .NET Core as part of an CMS 12 upgrade may seem daunting, but with proper guidance, it can be a smooth transition. By following the steps outlined in this guide, you can ensure that your application's URL rewriting functionality remains intact while embracing the capabilities of .NET Core.<br /><br />Remember, migration is an opportunity to optimize and improve your application for the future. So, embrace the change, and happy migrating!</div>HTTP/3: Fast and Securehttps://blog.paulmcgann.dev/2023/07/http3-fast-and-secure.html2023-07-09T20:31:00.0000000Z<h1 style="text-align: left;">HTTP/3 with Optimizely DXP</h1><p>In the ever-evolving landscape of web technologies, HTTP/3 emerges as a significant advancement, promising faster and more secure communication between clients and servers. As a solution architect, it's essential to stay up-to-date with the latest protocols and understand their implications. In this blog post, we will explore what HTTP/3 is, why it is important, and when we can expect its widespread availability.</p><h3 style="text-align: left;">What is HTTP/3?</h3><p>HTTP/3, also known as Hypertext Transfer Protocol version 3, is the next generation of the HTTP protocol. It is being developed to address the limitations and challenges faced by its predecessor, HTTP/2. HTTP/3 is designed to improve the efficiency of web communications by utilizing the User Datagram Protocol (UDP) instead of TCP (Transmission Control Protocol).</p><h3 style="text-align: left;">The Importance of HTTP/3:</h3><div><ul style="text-align: left;"><li>Enhanced Performance: HTTP/3 leverages the QUIC (Quick UDP Internet Connections) transport protocol, which significantly improves performance. QUIC allows for lower latency and reduced connection setup time, leading to faster loading times for web pages and improved overall browsing experience.</li><li>Improved Security: HTTP/3 incorporates encryption as a fundamental part of the protocol. This means that data sent using HTTP/3 is encrypted by default, enhancing security and privacy for users. Additionally, the use of QUIC's built-in encryption makes it more resistant to certain types of attacks compared to previous versions of HTTP.</li><li>Network-Friendly: UDP-based communication used in HTTP/3 ensures better network utilization by reducing the head-of-line blocking problem. This issue often occurs with TCP, where a packet delay or loss can affect the delivery of subsequent packets. HTTP/3's QUIC protocol eliminates this problem, allowing for parallel request and response multiplexing.</li><li>Seamless Migration: HTTP/3 maintains backward compatibility with previous versions of the HTTP protocol, making it easier for developers to transition their applications. Existing websites and APIs can gradually adopt HTTP/3 without requiring significant changes to the underlying infrastructure.</li></ul></div><h3 style="text-align: left;">When will HTTP/3 be available?</h3><p>HTTP/3 is still in development and actively being deployed by several organizations. However, its widespread availability depends on multiple factors, including browser and server support. As of the time of writing, several major browsers, including Chrome, Firefox, and Edge, have experimental support for HTTP/3. Cloudflare has added support for HTTP/3 and you can easily switch it on, below are some steps on how to achieve this.</p><h3 style="text-align: left;">Get started using HTTP/3</h3><p>HTTP/3 is set to revolutionize the web with its improved performance, enhanced security, and better network utilization. As solution architects, it's important to familiarize ourselves with this evolving protocol to leverage its benefits for our clients and users. While HTTP/3 is not widely available just yet, you can start to experiment with HTTP/3 by <a href="https://caniuse.com/?search=http%2F3" rel="nofollow" target="_blank">enabling </a>this in the three most major browsers.</p><p>Enabling HTTP/3 in Cloudflare involves a few straightforward steps. Here's a guide on how to enable HTTP/3 for your website:</p><ol style="text-align: left;"><li>Log in to your Cloudflare account: Access your Cloudflare account by visiting the Cloudflare website and entering your credentials.</li><li>Select your domain: Once logged in, select the domain for which you want to enable HTTP/3. This will open the dashboard for that particular domain.</li><li>Go to the "Network" tab: In the Cloudflare dashboard, navigate to the "Network" tab located at the top of the page. Click on it to access the network settings for your domain.</li><li>Enable HTTP/3: Within the "Network" tab, scroll down until you find the "HTTP/3" section. Toggle the switch to enable HTTP/3 for your domain.</li><li>Save and deploy changes: After enabling HTTP/3, click on the "Save" or "Deploy" button (the exact wording may vary) to save your changes and apply them to your domain.</li><li>Verify HTTP/3 activation: Once you have enabled HTTP/3, you can verify its activation by visiting your website and checking the network tab in your browser's developer tools. Look for the protocol being used for your website's requests. If it shows "h3" or "quic" instead of "http/1.1" or "http/2," it means HTTP/3 is successfully enabled.</li></ol><p>Please note that enabling HTTP/3 in Cloudflare relies on the browser and client support for HTTP/3. While major browsers have support for HTTP/3, not all clients may be compatible at the time of implementation. Cloudflare automatically negotiates the best protocol available between the client and their servers, falling back to HTTP/2 or HTTP/1.1 if HTTP/3 is not supported.</p><h2 style="text-align: left;">Optimizely DXP Support HTTP/3</h2><p>It is worth noting at the time of writing I reached out to Optimizely to see if it is possible to switch on / enable HTTP/3 in the DXP environment. Optimizely have advised that currently HTTP/3 is not supported in DXP and due to this they won't activate in cloudflare.</p><p>They did however suggest that I raise a feature request and the team will consider this in future developments.</p><p>I had a quick check of the feature requests first before creating a duplicate request and I found this has already been request. If you too would like this feature implemented I would suggest you vote for <a href="https://feedback.optimizely.com/ideas/DXCS-I-407" target="_blank">this request.</a></p><h2 style="text-align: left;">Conclusion</h2><p>In conclusion, HTTP/3 presents a significant advancement in web communication, offering improved performance, enhanced security, and better network utilization. As solution architects, it's crucial for us to understand the implications of this evolving protocol and leverage its benefits for our clients and users. While HTTP/3 is still being developed and its widespread availability depends on browser and server support, it is possible to start experimenting with HTTP/3 in Cloudflare.</p><p>Enabling HTTP/3 in Cloudflare involves straightforward steps outlined in the blog post. However, it's important to note that at the time of writing, Optimizely DXP does not support HTTP/3 in its environment. The Optimizely team suggests raising a feature request and encourages users to vote for its implementation.</p><p>As HTTP/3 continues to gain traction and more organizations adopt it, we can anticipate a future where faster, more secure, and efficient web communications become the norm. By staying informed and actively exploring the possibilities of HTTP/3, we can position ourselves as forward-thinking solution architects ready to embrace the future of web technologies.</p>Optimizely DXP Image Optimizationhttps://dev.to/paulmcgann/optimizely-dxp-image-optimization-2ip2021-11-16T21:04:51.0000000Z<p>Optimizely DXP is a cloud-first approach to customer engagement including high availability and performance, easy connectivity with other cloud services and existing systems, ability to manage spikes in customer demand, and a platform that is ready to seamlessly adopt the latest technology updates. </p>
<p>Optimizely DXP also includes a Content Delivery Network provided by Cloudflare for performance and security optimization.</p>
<p>I want to focus upon the optimization side of Cloudflare, more specifically image optimization.</p>
<p>Cloudflare has 3 products to help with image optimization, Cloudflare Image, Cloudflare Image Resizing, Cloudflare Polish. For the purpose of this post I will focus on Cloudflare Polish as this is enabled by default in Optimizely DXP, you can read more about the other products <a href="https://developers.cloudflare.com/images/">here</a>.</p>
<p>Cloudflare polish automatically optimizes images upon your site whenever is is fetched from you site.</p>
<p>Whenever you make the first request to retrieve and image you will notice within the headers the 'cf-cache-status' will be set to 'MISS'. Cloudflare is telling us it tried to retrieve the image from cache, however it did not find it and so requested it from the origin.</p>
<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iqsh8dCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6maq87yai5g399j4tqar.PNG" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iqsh8dCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6maq87yai5g399j4tqar.PNG" alt="Cloudflare CF-Cache-Status MISS" width="836" height="422" /></a></p>
<p>If you were to wait a few seconds and refresh the image you will notice that the 'cf-cache-status' is now 'HIT'.</p>
<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dFw_wBGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mfhvfo6uqhmh2ni2siq.PNG" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dFw_wBGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mfhvfo6uqhmh2ni2siq.PNG" alt="Cloudflare CF-Cache-Status HIT" width="839" height="558" /></a></p>
<p>To confirm that Polish has been applied we can check the 'cf-bgj' header returns the image quality setting 'imgq'. We can also check the 'cf-polished' header to confirm Polish status and returns the image quality setting (qual) and original size (origSize). </p>
<p>The above headers can confirm that polish is working working, however we can do better and ensure that images are being served in next generation format. To do this we can review the 'content-type' header and ensure this is returning 'image/webp', comparing this to the 'cf-polished' original format (origFmt) we can see the original format of the image was png and has been converted to webp.</p>
<p>While Cloudflare Polish automatically optimizes images, it will only optimize the image if the webp image is smaller than the original image.</p>
<p>If we review the below 'cf-polished' header below. We can see the status is set to 'webp_bigger' this is informing us that the image optimization on this image would provide no benefit.</p>
<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QddeN_83--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lq4jzcyb0cou8shqorc.PNG" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QddeN_83--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lq4jzcyb0cou8shqorc.PNG" alt="No Compression" width="841" height="494" /></a></p>
<p>Hopefully someone else finds this information useful. If you have any questions please drop a comment below.</p>
Working with Optimizely Projectshttps://dev.to/paulmcgann/working-with-optimizely-projects-2ojn2021-11-05T11:38:32.0000000Z<p>Optimizely has a featured called <a href="https://world.optimizely.com/documentation/developer-guides/CMS/projects/">'Projects'</a>, which allows editors to collaborate on content before scheduling to publish.</p>
<h3>
<a href="#problem">
</a>
Problem
</h3>
<p>While work on a project that imports hundreds, of pieces of content. These pieces of content are added to a project for the team to review and mark the items as ready to publish.</p>
<p>You can see the problem here in that with hundreds of pieces of content to review, marking each content piece as ready to publish could take some time.</p>
<h3>
<a href="#solution">
</a>
Solution
</h3>
<p>To over come the problem we decided to create a scheduled job that would loop over all items in a project and mark them as ready to publish.</p>
<p>To retrieve the projects and their project items we can get an instance of the 'ProjectRepository' into our scheduled job.</p>
<p>Once we have the project and its items we can then loop over the project items and use the 'IContentChangeManager' interface to update the status of the content items.<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="kt">var</span> <span class="n">projects</span> <span class="p">=</span> <span class="n">_projectRepository</span><span class="p">.</span><span class="nf">List</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">project</span> <span class="k">in</span> <span class="n">projects</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">projectItemIds</span> <span class="p">=</span> <span class="n">_projectRepository</span><span class="p">.</span><span class="nf">ListItems</span><span class="p">(</span><span class="n">project</span><span class="p">.</span><span class="n">ID</span><span class="p">).</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">ID</span><span class="p">);</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">int</span> <span class="n">projectItemId</span> <span class="k">in</span> <span class="n">projectItemIds</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">ProjectItem</span> <span class="n">projectItem</span> <span class="p">=</span> <span class="nf">GetProjectItem</span><span class="p">(</span><span class="n">projectItemId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">projectItem</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">CommitResult</span> <span class="n">commitResult</span> <span class="p">=</span> <span class="n">_contentChangeManager</span><span class="p">.</span><span class="nf">Commit</span><span class="p">(</span><span class="n">projectItem</span><span class="p">.</span><span class="n">ContentLink</span><span class="p">,</span> <span class="n">SaveAction</span><span class="p">.</span><span class="n">CheckIn</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">ProjectItem</span> <span class="nf">GetProjectItem</span><span class="p">(</span><span class="kt">int</span> <span class="n">projectItemId</span><span class="p">)</span> <span class="p">=></span> <span class="n">_projectRepository</span><span class="p">.</span><span class="nf">GetItem</span><span class="p">(</span><span class="n">projectItemId</span><span class="p">);</span>
</code></pre>
</div>
<p>I just wanted to share this piece of code with anyone who was trying to achieve something similiar.</p>
Translate Categories in Episerver using DbLocalizationProviderhttps://dev.to/paulmcgann/translate-categories-in-episerver-using-dblocalizationprovider-3c9d2021-01-21T20:52:53.0000000Z<p>While doing some investigation into localization with Episerver I came across a useful library that helps content editors easily translate resources that are normally stored in xml, by storing them within a database.</p>
<p>One of the areas that I investigated was translating the built in categories within Episerver. This is possible to do using a strongly-typed <a href="https://github.com/valdisiljuconoks/localization-provider-epi/blob/master/docs/working-with-resources-epi.md#translating-episerver-categories">category definition</a>.<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">[</span><span class="n">LocalizedCategory</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">SampleCategory</span> <span class="p">:</span> <span class="n">Category</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">SampleCategory</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Name</span> <span class="p">=</span> <span class="s">"This is sample cat. from code"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>This may be suitable in some cases, however in some scenarios where the categories already exist this may not be the best course of action.</p>
<p>The <a href="https://github.com/valdisiljuconoks/localization-provider-epi/blob/master/README.md">DbLocalizationProvider</a> has a way to <a href="https://github.com/valdisiljuconoks/LocalizationProvider/blob/master/docs/sync-net.md#register-manually-resources">manually create resources</a>. Using this I set about creating a resource manually for each category.</p>
<p>Each resource needs a unique resource key, the translation /value and the language the translation is for.</p>
<p>In order to create each resource I used a stored procedure that would iterate over the categories and build out a unique resource key, whilst assigning the value and language.<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="k">using</span> <span class="nn">DbLocalizationProvider.Sync</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">EPiServer.DataAbstraction</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">EPiServer.PlugIn</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">EPiServer.Scheduler</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Globalization</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">LangTransaltion.Business.Localization</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">ScheduledPlugIn</span><span class="p">(</span><span class="n">DisplayName</span> <span class="p">=</span> <span class="s">"Category Localization Scheduled Job"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">CategoryLocalizationScheduledJob</span> <span class="p">:</span> <span class="n">ScheduledJobBase</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">CategoryRepository</span> <span class="n">_categoryRepository</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ISynchronizer</span> <span class="n">_synchronizer</span><span class="p">;</span>
<span class="k">private</span> <span class="kt">bool</span> <span class="n">_stopSignaled</span><span class="p">;</span>
<span class="k">private</span> <span class="kt">int</span> <span class="n">_categoryCounterTotal</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="k">private</span> <span class="n">IList</span><span class="p"><</span><span class="n">ManualResource</span><span class="p">></span> <span class="n">_allCategories</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">CategoryLocalizationScheduledJob</span><span class="p">(</span><span class="n">CategoryRepository</span> <span class="n">categoryRepository</span><span class="p">,</span> <span class="n">ISynchronizer</span> <span class="n">synchronizer</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IsStoppable</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">_categoryRepository</span> <span class="p">=</span> <span class="n">categoryRepository</span><span class="p">;</span>
<span class="n">_synchronizer</span> <span class="p">=</span> <span class="n">synchronizer</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Called when a user clicks on Stop for a manually started job, or when ASP.NET shuts down.</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Stop</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_stopSignaled</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Called when a scheduled job executes</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <returns>A status message to be stored in the database log and visible from admin mode</returns></span>
<span class="k">public</span> <span class="k">override</span> <span class="kt">string</span> <span class="nf">Execute</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">OnStatusChanged</span><span class="p">(</span><span class="s">$"Starting execution of </span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nf">GetType</span><span class="p">()}</span><span class="s">"</span><span class="p">);</span>
<span class="n">_allCategories</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">ManualResource</span><span class="p">>();</span>
<span class="kt">var</span> <span class="n">root</span> <span class="p">=</span> <span class="n">_categoryRepository</span><span class="p">.</span><span class="nf">GetRoot</span><span class="p">();</span>
<span class="nf">IterateCategories</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="s">$"Categories.</span><span class="p">{</span><span class="n">root</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="n">_synchronizer</span><span class="p">.</span><span class="nf">RegisterManually</span><span class="p">(</span><span class="n">_allCategories</span><span class="p">);</span>
<span class="k">return</span> <span class="s">$"</span><span class="p">{</span><span class="n">_categoryCounterTotal</span><span class="p">}</span><span class="s"> categories checked"</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Iterates over categories build a unique key</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="currentParent"></param></span>
<span class="c1">/// <param name="path"></param></span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">IterateCategories</span><span class="p">(</span><span class="n">Category</span> <span class="n">currentParent</span><span class="p">,</span> <span class="kt">string</span> <span class="n">path</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">categories</span> <span class="p">=</span> <span class="n">currentParent</span><span class="p">.</span><span class="n">Categories</span><span class="p">.</span><span class="nf">ToList</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">category</span> <span class="k">in</span> <span class="n">categories</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_stopSignaled</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">_categoryCounterTotal</span><span class="p">++;</span>
<span class="nf">OnStatusChanged</span><span class="p">(</span><span class="s">$"Working on category </span><span class="p">{</span><span class="n">category</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">, child of: </span><span class="p">{</span><span class="n">currentParent</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="n">_allCategories</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">ManualResource</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">path</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">category</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">category</span><span class="p">.</span><span class="n">Description</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">CultureInfo</span><span class="p">(</span><span class="s">"en"</span><span class="p">)));</span>
<span class="nf">IterateCategories</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">path</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="n">category</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>After running the scheduled job the new resources will be added to the database.</p>
<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aMBDwMPl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/knqhifult9weu0oghnht.PNG" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aMBDwMPl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/knqhifult9weu0oghnht.PNG" alt="DbLocalizationProvider" /></a></p>
<p>The final piece of the puzzle was to retrieve the resource key whenever we use the category on the site. In order to do this I created an extension method to return the resource key based on the current category.<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code>
<span class="k">using</span> <span class="nn">EPiServer.DataAbstraction</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">LangTransaltion.Helpers</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">CategoryHelper</span>
<span class="p">{</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Gets the category resource key.</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="category"></param></span>
<span class="c1">/// <returns></returns></span>
<span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">GetResourceKey</span><span class="p">(</span><span class="k">this</span> <span class="n">Category</span> <span class="n">category</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">categoryList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">string</span><span class="p">>();</span>
<span class="nf">GetParentCategories</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">categoryList</span><span class="p">);</span>
<span class="n">categoryList</span><span class="p">.</span><span class="nf">Reverse</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">resourceKey</span> <span class="p">=</span> <span class="s">$"Categories.</span><span class="p">{</span><span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">"."</span><span class="p">,</span> <span class="n">categoryList</span><span class="p">)}</span><span class="s">"</span><span class="p">;</span>
<span class="k">return</span> <span class="n">resourceKey</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Gets the parent categories, starting a from a root category.</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="currentCategory"></param></span>
<span class="c1">/// <param name="categoryList"></param></span>
<span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">GetParentCategories</span><span class="p">(</span><span class="n">Category</span> <span class="n">currentCategory</span><span class="p">,</span> <span class="n">List</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="n">categoryList</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">categoryList</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">currentCategory</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">currentCategory</span><span class="p">.</span><span class="n">Parent</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">parent</span> <span class="p">=</span> <span class="n">currentCategory</span><span class="p">.</span><span class="n">Parent</span><span class="p">;</span>
<span class="nf">GetParentCategories</span><span class="p">(</span><span class="n">parent</span><span class="p">,</span> <span class="n">categoryList</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Now when you are using a category and wish to retrieve the translation you can simply use<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight csharp"><code><span class="p">...</span>
<span class="n">DbLocalizationProvider</span><span class="p">.</span><span class="n">LocalizationProvider</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="n">category</span><span class="p">.</span><span class="nf">GetResourceKey</span><span class="p">())</span>
<span class="p">...</span>
</code></pre>
</div>
<p>I would like to thank the <a href="https://twitter.com/tech_fellow">Valdis Iljuconoks</a> for helping me out and answering some questions I had along the way.</p>
Episerver Visitor Group Configurationhttp://spottyslug.wordpress.com/?p=22018-12-11T11:58:22.0000000ZConfiguring episerver visitor groups geolocation criteria.Episerver Visitor Group Configurationhttp://spottyslug.wordpress.com/?p=22018-12-11T10:58:22.0000000ZConfiguring episerver visitor groups geolocation criteria.