<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Maris Krivtezs</title><link href="http://world.optimizely.com" /><updated>2021-12-23T00:00:00.0000000Z</updated><id>https://world.optimizely.com/blogs/Maris-Krivtezs/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Upgraded Geta Optimizely packages</title><link href="http://marisks.net/2021/12/23/upgraded-optimizely-packages/" /><id>&lt;h1 id=&quot;geta-optimizely-extensions&quot;&gt;Geta.Optimizely.Extensions&lt;/h1&gt;
&lt;p&gt;The old package &lt;em&gt;Geta.EPi.Extensions&lt;/em&gt; has been renamed to &lt;em&gt;Geta.Optimizely.Extensions&lt;/em&gt;. Install it using Nuget package manager or from the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-Package Geta.Optimizely.Extensions
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The package has almost the same functionality as the previous version. The only change is that we have removed &lt;code&gt;XForms&lt;/code&gt; related functionality as it is not supported in &lt;em&gt;Optimizely 12&lt;/em&gt; anymore.&lt;/p&gt;
&lt;p&gt;For more information check the &lt;a href=&quot;https://github.com/Geta/geta-optimizely-extensions&quot;&gt;Github page&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;sitemaps-and-sitemaps-commerce&quot;&gt;Sitemaps and Sitemaps.Commerce&lt;/h1&gt;
&lt;p&gt;The old packages &lt;em&gt;Geta.SEO.Sitemaps&lt;/em&gt; and &lt;em&gt;Geta.SEO.Sitemaps.Commerce&lt;/em&gt; has been renamed to &lt;em&gt;Geta.Optimizely.Sitemaps&lt;/em&gt; and &lt;em&gt;Geta.Optimizely.Sitemaps.Commerce&lt;/em&gt;. Install those using Nuget package manager or from the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-Package Geta.Optimizely.Sitemaps
Install-Package Geta.Optimizely.Sitemaps.Commerce
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After installation, you have to configure it. Register the &lt;em&gt;Sitemaps&lt;/em&gt; and/or &lt;em&gt;Sitemaps Commerce&lt;/em&gt; in the service collection first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddSitemaps(x =&amp;gt;
{
  x.EnableLanguageDropDownInAdmin = false;
  x.EnableRealtimeCaching = true;
  x.EnableRealtimeSitemap = false;
});
services.AddSitemapsCommerce();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then make sure that you have razor pages mapped:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.UseEndpoints(endpoints =&amp;gt;
{
    endpoints.MapRazorPages();
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that, you will see a new top menu item for the &lt;em&gt;Sitemaps&lt;/em&gt; in the &lt;em&gt;Optimizely&lt;/em&gt; administration UI. There is a new administration user interface, but it has the same functionality as the old one.&lt;/p&gt;
&lt;p&gt;For more information check the &lt;a href=&quot;https://github.com/Geta/geta-optimizely-sitemaps&quot;&gt;Github page&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;geta-optimizely-googleproductfeed&quot;&gt;Geta.Optimizely.GoogleProductFeed&lt;/h1&gt;
&lt;p&gt;The package &lt;em&gt;Geta.GoogleProductFeed&lt;/em&gt; has been renamed to &lt;em&gt;Geta.Optimizely.GoogleProductFeed&lt;/em&gt; so that all the naming is consistent with other &lt;em&gt;Optimizely&lt;/em&gt; packages. Install it using Nuget package manager or from the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-Package Geta.Optimizely.GoogleProductFeed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After installation, you have to configure it. Register the &lt;em&gt;GoogleProductFeed&lt;/em&gt; in the service collection first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddGoogleProductFeed(x =&amp;gt;
{
    x.ConnectionString = _configuration.GetConnectionString(&amp;quot;EPiServerDB&amp;quot;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then you have to create your &lt;code&gt;FeedBuilder&lt;/code&gt;. See the &lt;a href=&quot;https://github.com/Geta/geta-optimizely-googleproductfeed&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once, it is created register it in the service collection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddTransient&amp;lt;FeedBuilder, MyFeedBuilder&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more information check the &lt;a href=&quot;https://github.com/Geta/geta-optimizely-googleproductfeed&quot;&gt;Github page&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;geta-optimizely-contenttypeicons&quot;&gt;Geta.Optimizely.ContentTypeIcons&lt;/h1&gt;
&lt;p&gt;We had a package &lt;em&gt;Geta.Epi.FontThumbnail&lt;/em&gt; for the older Episerver/Optimizely versions. The package name did not describe what it was. So we renamed it to &lt;em&gt;Geta.Optimizely.ContentTypeIcons&lt;/em&gt;. Install it using Nuget package manager or from the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-Package Geta.Optimizely.ContentTypeIcons
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After installation, you have to configure it. Register the &lt;em&gt;ContentTypeIcons&lt;/em&gt; in the service collection first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddContentTypeIcons(x =&amp;gt;
{
    x.EnableTreeIcons = true;
    x.ForegroundColor = &amp;quot;#ffffff&amp;quot;;
    x.BackgroundColor = &amp;quot;#02423F&amp;quot;;
    x.FontSize = 40;
    x.CachePath = &amp;quot;[appDataPath]\\thumb_cache\\&amp;quot;;
    x.CustomFontPath = &amp;quot;[appDataPath]\\fonts\\&amp;quot;;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you can start using it by adding the &lt;code&gt;ContentTypeIcon&lt;/code&gt; attribute to your content types. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ContentTypeIcon(FontAwesome5Brands.Github)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This attribute on a page type will show you a &lt;em&gt;Github&lt;/em&gt; icon when you will create a new page of this page type. Also, if you have the &lt;code&gt;EnableTreeIcons&lt;/code&gt; setting enabled, you will see the icon in the content tree.&lt;/p&gt;
&lt;p&gt;You can also use the &lt;code&gt;TreeIcon&lt;/code&gt; attribute to override the behavior for the content tree. You can set a different icon or ignore the icon completely:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[TreeIcon(Ignore = true)]
[TreeIcon(FontAwesome5Solid.CheckDouble)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more information check the &lt;a href=&quot;https://github.com/Geta/geta-optimizely-contenttypeicons&quot;&gt;Github page&lt;/a&gt;.&lt;/p&gt;
</id><updated>2021-12-23T00:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>New Geta.NotFoundHandler</title><link href="http://marisks.net/2021/11/04/new-geta-notfoundhandler/" /><id>&lt;h1 id=&quot;the-new-name-geta-notfoundhandler&quot;&gt;The new name - Geta.NotFoundHandler&lt;/h1&gt;
&lt;p&gt;We have changed the name of the package again. The reason for this is that the old name was inconsistent throughout the code. The assembly name was &lt;code&gt;Geta.404Handler&lt;/code&gt;, but namespaces started with &lt;code&gt;Geta.NotFoundHandler&lt;/code&gt;. We could have changed namespaces to have &lt;code&gt;404Handler&lt;/code&gt; in the name, but namespaces&amp;#39; parts in .NET can&amp;#39;t start with a number.&lt;/p&gt;
&lt;h1 id=&quot;asp-net-5-and-optimizely&quot;&gt;ASP.NET 5 and Optimizely&lt;/h1&gt;
&lt;p&gt;After reviewing the library, we found that it was not so dependent on &lt;em&gt;Optimizely&lt;/em&gt; (&lt;em&gt;Episerver&lt;/em&gt;). So we refactored the library that supports ASP.NET 5 and added administrative UI integration in &lt;em&gt;Optimizely&lt;/em&gt;. Now there are three separate packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Geta.NotFoundHandler&lt;/code&gt; - the core library for ASP.NET 5.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Geta.NotFoundHandler.Admin&lt;/code&gt; - the administrative UI.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Geta.NotFoundHandler.Optimizely&lt;/code&gt; - a module that integrates administrative UI in Optimizely UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;getting-started-in-asp-net&quot;&gt;Getting started in ASP.NET&lt;/h1&gt;
&lt;p&gt;In an ASP.NET 5 project install &lt;code&gt;Geta.NotFoundHandler.Admin&lt;/code&gt; and in the &lt;code&gt;Startup.ConfigureServices&lt;/code&gt; method configure the &lt;em&gt;NotFoundHandler&lt;/em&gt;. You have to provide a connection string at least.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
    var connectionString = ... // Retrieve connection string here
    services.AddNotFoundHandler(o =&amp;gt;
    {
        o.UseSqlServer(connectionstring);
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, users in the &lt;em&gt;Administrators&lt;/em&gt; role will have access to the administration UI. You can change the default policy in the configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
    var connectionString = ... // Retrieve connection string here
    services.AddNotFoundHandler(o =&amp;gt;
    {
        o.UseSqlServer(connectionstring);
    }, policy =&amp;gt;
    {
        policy.RequireRole(&amp;quot;MyRole&amp;quot;);
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, initialize &lt;em&gt;NotFoundHandler&lt;/em&gt; in the &lt;code&gt;Configure&lt;/code&gt; method as the first registration. It will make sure that the handler will catch all 404 errors.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void Configure(IApplicationBuilder app)
{
    app.UseNotFoundHandler();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you should be able to access administrative UI by &lt;code&gt;https://mysite/Geta.NotFoundHandler.Admin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2021-11/redirects.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1 id=&quot;getting-started-in-optimizely&quot;&gt;Getting started in Optimizely&lt;/h1&gt;
&lt;p&gt;In an Optimizely project install &lt;code&gt;Geta.NotFoundHandler.Optimizely&lt;/code&gt; package. Then configure the handler in &lt;code&gt;Startup.ConfigureServices&lt;/code&gt; method. Set the connection string and add access for the CMS administrators.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
    var connectionString = ... // Retrieve connection string here
    services.AddNotFoundHandler(o =&amp;gt;
    {
        o.UseSqlServer(connectionString);
    }, policy =&amp;gt;
    {
        policy.RequireRole(Roles.CmsAdmins);
    });
    services.AddOptimizelyNotFoundHandler();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As in the ASP.NET case, initialize &lt;em&gt;NotFoundHandler&lt;/em&gt; in the &lt;code&gt;Configure&lt;/code&gt; method as the first registration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void Configure(IApplicationBuilder app)
{
    app.UseNotFoundHandler();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run the application and in the &lt;em&gt;Optimizely&lt;/em&gt; administrative UI you will find a link to the &lt;em&gt;NotFounHandler&lt;/em&gt; administrative UI in the top menu.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2021-11/optimizely-redirects.jpg&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;NotFoundHandler&lt;/em&gt; now is ready for use in the new &lt;em&gt;Optimizely&lt;/em&gt; 12, but if you are using older versions of &lt;em&gt;Optimizely/Episerver&lt;/em&gt;, then we will still support those as &lt;a href=&quot;https://github.com/Geta/404handler&quot;&gt;Geta.404Handler&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source code and documentation for the new &lt;em&gt;NotFoundHandler&lt;/em&gt; is available on &lt;a href=&quot;https://github.com/Geta/geta-notfoundhandler&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</id><updated>2021-11-04T00:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Managing settings in Episerver project - part 3</title><link href="http://marisks.net/2020/03/31/managing-settings-in-episerver-project-3/" /><id>&lt;h1 id=&quot;settings-page&quot;&gt;Settings page&lt;/h1&gt;
&lt;p&gt;If we look at the single responsibility principle, a start page with settings is doing too much. It has start page related content and also site-wide settings. When you have a large site, the start page becomes a settings page - the primary purpose of it is settings management. But that is not a good approach. The start page should contain only the page related stuff.&lt;/p&gt;
&lt;p&gt;To solve the issue, create a separate page type that has only one purpose - storing settings. You can group settings properties or add local blocks to this page type, as I described in my previous &lt;a href=&quot;/2020/02/28/managing-settings-in-episerver-project-2/&quot;&gt;article&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;16BA6D0E-49D1-4A49-95A9-88B7FAE65E63&amp;quot;)]
public class SettingsPage : PageData
{
  [Display(GroupName = GroupNames.Header, Order = 10)]
  [UIHint(UIHint.Image)]
  [AllowedTypes(typeof(ImageFile))]
  public virtual ContentReference CompanyLogo { get; set; }

  [Display(GroupName = GroupNames.Footer, Order = 10)]
  public virtual LinkBlock Links { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can create a page in the root of the site and set required settings. To load and use the page in your code, you can use an extension method from the &lt;a href=&quot;https://github.com/Geta/EPi.Extensions&quot;&gt;&lt;code&gt;Geta.EPi.Extensions&lt;/code&gt; package&lt;/a&gt; &lt;code&gt;GetFirstChild&lt;/code&gt; or create one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static T GetFirstChild&amp;lt;T&amp;gt;
  this IContentLoader contentLoader, ContentReference contentReference)
    where T : IContentData
{
  return contentLoader.GetChildren&amp;lt;T&amp;gt;(contentReference).FirstOrDefault();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your code, use this extension to load settings from the site root.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var settings = _contentLoader.GetFirstChild&amp;lt;SettingsPage&amp;gt;(ContentReference.StartPage);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;multiple-sites&quot;&gt;Multiple sites&lt;/h1&gt;
&lt;p&gt;As you have the settings page type already created, you need to create new settings pages on each site. Then when you load the settings by using &lt;code&gt;ContentReference.StartPage&lt;/code&gt; in your code, it will load the correct settings page for each of your sites.&lt;/p&gt;
&lt;h1 id=&quot;global-settings&quot;&gt;Global settings&lt;/h1&gt;
&lt;p&gt;You can use the same approach for global settings. Create a separate page type that will contain global settings. Then create this page in the root of the &lt;em&gt;Episerver&lt;/em&gt; instance.&lt;/p&gt;
&lt;p&gt;To load global settings, use &lt;code&gt;ContentReference.RootPage&lt;/code&gt; as the settings page parent in &lt;code&gt;GetFirstChild&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var settings = _contentLoader.GetFirstChild&amp;lt;GlobalSettingsPage&amp;gt;(ContentReference.RootPage);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;A custom page type is an excellent tool when you have to separate settings from other site data. You can create a site-specific settings page type, a settings page type that is used in multiple sites, or a page type that is used globally in the whole &lt;em&gt;Episerver&lt;/em&gt; instance.&lt;/p&gt;
</id><updated>2020-03-31T00:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Managing settings in Episerver project - part 3</title><link href="http://marisks.net/2020/03/31/managing-settings-in-episerver-project-3/" /><id>&lt;h1 id=&quot;settings-page&quot;&gt;Settings page&lt;/h1&gt;
&lt;p&gt;If we look at the single responsibility principle, a start page with settings is doing too much. It has start page related content and also site-wide settings. When you have a large site, the start page becomes a settings page - the primary purpose of it is settings management. But that is not a good approach. The start page should contain only the page related stuff.&lt;/p&gt;
&lt;p&gt;To solve the issue, create a separate page type that has only one purpose - storing settings. You can group settings properties or add local blocks to this page type, as I described in my previous &lt;a href=&quot;/2020/02/28/managing-settings-in-episerver-project-2/&quot;&gt;article&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;16BA6D0E-49D1-4A49-95A9-88B7FAE65E63&amp;quot;)]
public class SettingsPage : PageData
{
  [Display(GroupName = GroupNames.Header, Order = 10)]
  [UIHint(UIHint.Image)]
  [AllowedTypes(typeof(ImageFile))]
  public virtual ContentReference CompanyLogo { get; set; }

  [Display(GroupName = GroupNames.Footer, Order = 10)]
  public virtual LinkBlock Links { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can create a page in the root of the site and set required settings. To load and use the page in your code, you can use an extension method from the &lt;a href=&quot;https://github.com/Geta/EPi.Extensions&quot;&gt;&lt;code&gt;Geta.EPi.Extensions&lt;/code&gt; package&lt;/a&gt; &lt;code&gt;GetFirstChild&lt;/code&gt; or create one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static T GetFirstChild&amp;lt;T&amp;gt;
  this IContentLoader contentLoader, ContentReference contentReference)
    where T : IContentData
{
  return contentLoader.GetChildren&amp;lt;T&amp;gt;(contentReference).FirstOrDefault();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your code, use this extension to load settings from the site root.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var settings = _contentLoader.GetFirstChild&amp;lt;SettingsPage&amp;gt;(ContentReference.StartPage);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;multiple-sites&quot;&gt;Multiple sites&lt;/h1&gt;
&lt;p&gt;As you have the settings page type already created, you need to create new settings pages on each site. Then when you load the settings by using &lt;code&gt;ContentReference.StartPage&lt;/code&gt; in your code, it will load the correct settings page for each of your sites.&lt;/p&gt;
&lt;h1 id=&quot;global-settings&quot;&gt;Global settings&lt;/h1&gt;
&lt;p&gt;You can use the same approach for global settings. Create a separate page type that will contain global settings. Then create this page in the root of the &lt;em&gt;Episerver&lt;/em&gt; instance.&lt;/p&gt;
&lt;p&gt;To load global settings, use &lt;code&gt;ContentReference.RootPage&lt;/code&gt; as the settings page parent in &lt;code&gt;GetFirstChild&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var settings = _contentLoader.GetFirstChild&amp;lt;GlobalSettingsPage&amp;gt;(ContentReference.RootPage);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;A custom page type is an excellent tool when you have to separate settings from other site data. You can create a site-specific settings page type, a settings page type that is used in multiple sites, or a page type that is used globally in the whole &lt;em&gt;Episerver&lt;/em&gt; instance.&lt;/p&gt;
</id><updated>2020-03-31T00:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Managing settings in Episerver project - part 2</title><link href="http://marisks.net/2020/02/28/managing-settings-in-episerver-project-2/" /><id>&lt;h1 id=&quot;grouping-settings-in-tabs&quot;&gt;Grouping settings in tabs&lt;/h1&gt;
&lt;p&gt;The easiest way to improve the way how settings are displayed for the users (administrators), is by grouping those in tabs. For example, you have several setting properties on the start page, and those are mixed now with other properties.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;6C709414-18C6-4E97-9B83-7220FA87D05A&amp;quot;, Order = 10, AvailableInEditMode = true)]
public class StartPage : PageData
{
    [CultureSpecific]
    public virtual string Header { get; set; }

    public virtual Url FacebookUrl { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Header&lt;/code&gt; property, in this example, is displayed on the start page, but &lt;code&gt;FacebookUrl&lt;/code&gt; is used for the whole site. So the last one is a setting we would not like to see mixed with start page related stuff in UI.&lt;/p&gt;
&lt;p&gt;By adding the &lt;code&gt;Display&lt;/code&gt; attribute and setting &lt;code&gt;GroupName&lt;/code&gt; property on it, we will move the setting property to the separate tab in UI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;6C709414-18C6-4E97-9B83-7220FA87D05A&amp;quot;, Order = 10, AvailableInEditMode = true)]
public class StartPage : PageData
{
    [CultureSpecific]
    public virtual string Header { get; set; }

    [Display(GroupName = &amp;quot;Site Settings&amp;quot;)]
    public virtual Url FacebookUrl { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is also a good approach to extract group name constants into a separate class and add &lt;code&gt;GroupDefinitions&lt;/code&gt; attribute so that &lt;em&gt;Episerver&lt;/em&gt; will pick it up. For more info, read &lt;a href=&quot;/link/99e06ec8c73243f18cc50b2f00bfe71f.aspx&quot;&gt;&lt;em&gt;Episerver&lt;/em&gt; documentation&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[GroupDefinitions]
public static class GroupNames
{
    [Display(GroupName = &amp;quot;Site settings&amp;quot;, Order = 10)]
    public const string SiteSettings = &amp;quot;SiteSettings&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;grouping-settings-with-local-blocks&quot;&gt;Grouping settings with local blocks&lt;/h1&gt;
&lt;p&gt;While grouping with a &lt;code&gt;Display&lt;/code&gt; attribute allows separating settings in UI, in code, the setting properties are kept together with content properties. One way to work around this issue is by creating blocks and adding those as properties to the start page.&lt;/p&gt;
&lt;p&gt;Create a block first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;13304E95-B697-444E-B0D4-F8806B70BF20&amp;quot;)]
public class SettingsBlock : BlockData
{
    public virtual Url FacebookUrl { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then add it to the start page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ContentType(GUID = &amp;quot;6C709414-18C6-4E97-9B83-7220FA87D05A&amp;quot;, Order = 10, AvailableInEditMode = true)]
public class StartPage : PageData
{
    [CultureSpecific]
    public virtual string Header { get; set; }

    [Display(GroupName = GroupNames.SiteSettings)]
    public virtual SettingsBlock Settings { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you see, I have kept the &lt;code&gt;Display&lt;/code&gt; attribute and set &lt;code&gt;GroupName&lt;/code&gt; so that the block with settings is still displayed in a separate tab.&lt;/p&gt;
&lt;p&gt;Now, when you need to use settings in your code, you can just pass &lt;code&gt;SettingsBlock&lt;/code&gt; around and do not mess with other start page related settings.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var startPage = _contentLoader.Get(ContentReference.StartPage);
var settings = startPage.Settings;

DoSomething(settings);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;As you see, there are some simple ways how to improve settings on your site for both - end-users in UI and developers in code. Though in large projects, it is not enough to add grouping or extract settings into a local block. You might need a more advanced approach. But that&amp;#39;s for another time.&lt;/p&gt;
&lt;p&gt;P.S. Check out &lt;a href=&quot;https://talk.alfnilsson.se/2014/04/01/creating-modular-settings-with-blocks/&quot;&gt;Alf&amp;#39;s blog&lt;/a&gt; about his approach to managing settings.&lt;/p&gt;
</id><updated>2020-02-28T00:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Managing settings in Episerver project - part 1</title><link href="http://marisks.net/2020/01/06/managing-settings-in-episerver-project-1/" /><id>&lt;h1 id=&quot;appsettings-in-web-config&quot;&gt;appSettings in Web.config&lt;/h1&gt;
&lt;p&gt;In the ASP.NET world (.NET Framework, not Core), the most common place for settings is the &lt;code&gt;appSettings&lt;/code&gt; section in the &lt;em&gt;Web.config&lt;/em&gt; file. You can configure solution-wide settings here. It is possible to add transformations for different environments where you are deploying the application.&lt;/p&gt;
&lt;p&gt;Also, you can add &amp;quot;partial&amp;quot; settings in a separate file. This is useful when each developer has their own settings. Just add the &amp;quot;partial&amp;quot; file to the &lt;code&gt;.gitignore&lt;/code&gt;, and in the &lt;em&gt;Web.config&lt;/em&gt; on the &lt;em&gt;appSettings&lt;/em&gt; tag, add &lt;code&gt;file&lt;/code&gt; attribute that points to your &amp;quot;partial&amp;quot; settings file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-xml&quot;&gt;&amp;lt;appSettings file=&amp;quot;appSettings.dev.config&amp;quot;&amp;gt;
  &amp;lt;add key=&amp;quot;MySetting&amp;quot; value=&amp;quot;The configuration&amp;quot;&amp;gt;
&amp;lt;/appSettings&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you configured your application, you can retrieve settings using &lt;code&gt;ConfigurarionManager&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var mySetting = ConfigurationManager.AppSettings[&amp;quot;MySetting&amp;quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It returns a string value. If you need another type, you have to cast to it.&lt;/p&gt;
&lt;h2 id=&quot;pros&quot;&gt;Pros&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Built-in ASP.NET feature.&lt;/li&gt;
&lt;li&gt;Easy to use.&lt;/li&gt;
&lt;li&gt;Transformations allow creating separate setting values for different environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cons&quot;&gt;Cons&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Unable to change values in runtime. Requires re-deploy.&lt;/li&gt;
&lt;li&gt;By default, everything is a string. You need to cast to other types.&lt;/li&gt;
&lt;li&gt;Unable to store complex types without serializing to some string format (comma-separated strings, XML, JSON).&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;custom-settings-section-in-web-config&quot;&gt;Custom settings section in Web.config&lt;/h1&gt;
&lt;p&gt;A custom settings section is a good alternative to &lt;code&gt;appSettings&lt;/code&gt;. It is harder to implement and use, but has several benefits over &lt;code&gt;appSettings&lt;/code&gt;. With it, you do not mix your settings with other application settings.&lt;/p&gt;
&lt;p&gt;I will not cover creating a custom configuration section here, but Joel Abrahamsson has a excellent article about it: &lt;a href=&quot;http://joelabrahamsson.com/creating-a-custom-configuration-section-in-net/&quot;&gt;Creating a custom Configuration Section in .NET&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;pros&quot;&gt;Pros&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Built-in ASP.NET feature.&lt;/li&gt;
&lt;li&gt;Separates your settings from other application settings.&lt;/li&gt;
&lt;li&gt;Transformations allow creating separate setting values for different environments.&lt;/li&gt;
&lt;li&gt;You can create complex types using configuration Elements and Collections.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cons&quot;&gt;Cons&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;More complex implementation and usage than &lt;code&gt;appSettings&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unable to change values in runtime. Requires re-deploy.&lt;/li&gt;
&lt;li&gt;Properties of the configuration are still just strings, and you need to cast those to other types.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;settings-on-the-start-page&quot;&gt;Settings on the Start page&lt;/h1&gt;
&lt;p&gt;It is quite common to use a start page as setting storage in Episerver projects. You can add properties on the start page and configure it to display on the separate tab.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[Display(GroupName = GroupNames.Settings, Order = 100)]
public virtual ContentReference MyLink { get; set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can use Episerver API to load the start page and use the setting.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var startPage = _contentLoader.Get&amp;lt;StartPage&amp;gt;(ContentReference.StartPage);
var myLink = startPage.MyLink;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When using a start page, you can define properties of any type Episerver has support. You can use integers, strings, content references, or even use blocks for complex types. A full list of supported types can be found here: &lt;a href=&quot;/link/52e3713caeb44099beb7fe421304c130.aspx&quot;&gt;Built-in property types&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another advantage of using the start page is UI. You can easily change settings by opening the start page, changing settings, and then publishing the page. For example, it can be used for &amp;quot;feature switching&amp;quot; to enable/disable some functionality on the site.&lt;/p&gt;
&lt;h2 id=&quot;pros&quot;&gt;Pros&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Easy to use Episerver API.&lt;/li&gt;
&lt;li&gt;Multi-language settings.&lt;/li&gt;
&lt;li&gt;It can be changed in runtime.&lt;/li&gt;
&lt;li&gt;Configure from UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cons&quot;&gt;Cons&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Environment specific configuration should be configured manually in each environment.&lt;/li&gt;
&lt;li&gt;The start page is not specifically built for settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;As you see in this article, each approach for storing and using settings has its advantages and disadvantages. So all approaches also have their use cases.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;appSettings&lt;/code&gt; and custom configuration sections are great for a configuration that differs by the environment. Also, you would not want to setup settings that often change in &lt;code&gt;Web.config&lt;/code&gt;. So this is the right place for settings that are required for 3rd party API integration, some connection details, etc.&lt;/p&gt;
&lt;p&gt;The start page can be used for settings that you might change often or should be changed at runtime. As I mentioned previously, it is the right place for &amp;quot;feature switching.&amp;quot; Also, you would not want to setup settings on the start page when you need those different in different environments.&lt;/p&gt;
&lt;p&gt;The right decision where and how to store settings should be made by a project team depending on requirements.&lt;/p&gt;
&lt;p&gt;In the next article, I will describe other options you can use and better organize settings in Episerver.&lt;/p&gt;
</id><updated>2020-01-06T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Getting promotion value from the order</title><link href="http://marisks.net/2019/10/23/getting-promotion-value-from-order/" /><id>&lt;p&gt;&lt;code&gt;IOrderForm&lt;/code&gt; has a property with all promotions applied to the order. Each promotion record provides information about the saved monetary amount, about the type of the reward, has a reference to the promotion data and more information. But it lacks information about the percentage value of promotion if a promotion&amp;#39;s type is a percentage.&lt;/p&gt;
&lt;p&gt;The only way to get the percentage value of the promotion is by loading the promotion by a reference and then getting its value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static decimal GetDiscountValue(this PromotionInformation promotionInfo, IContentLoader contentLoader)
{
    if (promotionInfo.RewardType != RewardType.Percentage)
    {
        return promotionInfo.SavedAmount;
    }

    if (contentLoader.TryGet&amp;lt;PromotionData&amp;gt;(promotionInfo.PromotionGuid, out var promotion)
        &amp;amp;&amp;amp; promotion is IMonetaryDiscount monetaryDiscount)
    {
        return monetaryDiscount.Discount.Percentage;
    }

    return promotionInfo.SavedAmount;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I have created an extension method to get the correct value based on a reward type. When the reward type is not a percentage, return saved amount value from the promotion. Otherwise, load the promotion and return percentage value. Only the promotion of &lt;code&gt;IMonetaryDiscount&lt;/code&gt; can have a percentage value.&lt;/p&gt;
&lt;p&gt;Now you can use this method to get a percentage or monetary value like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public void WorkOnPromotionValue(IOrderForm orderForm, IContentLoader contentLoader)
{
    var promotion = orderForm.Promotions.First();
    var value = promotion.GetDiscountValue(contentLoader);

    if (promotion.RewardType == RewardType.Percentage)
    {
        var percentage = value;
        // Do some work with percentage ...
    }
    else
    {
        var money = value;
        // Do some work with money ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be much better if Episerver would store percentage value in the order too. Promotions might be removed, and it will not be possible to get the percentage value anymore. And promotions are removed quite often as too many promotions severely affect the site&amp;#39;s performance when calculating discounted price.&lt;/p&gt;
</id><updated>2019-10-23T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>A more flexible way to hide category property in Episerver</title><link href="http://marisks.net/2019/08/02/hiding-category-property/" /><id>&lt;p&gt;In &lt;em&gt;Episerver&lt;/em&gt; you can hide properties in edit mode using a &lt;code&gt;ScaffoldColumn&lt;/code&gt; attribute by using it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ScaffoldColumn(false)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does not work for the &lt;code&gt;Category&lt;/code&gt; property though. To fix it, you can add an editor descriptor similar to &lt;em&gt;Joel&amp;#39;s&lt;/em&gt; but instead of filtering on content types, filter on &lt;code&gt;ScaffoldColumn&lt;/code&gt; and use its &lt;code&gt;Scaffold&lt;/code&gt; value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[EditorDescriptorRegistration(TargetType = typeof (CategoryList))]
public class HideCategoryEditorDescriptor : EditorDescriptor
{
    public override void ModifyMetadata(
        ExtendedMetadata metadata,
        IEnumerable&amp;lt;Attribute&amp;gt; attributes)
    {
        var attrs = attributes as Attribute[] ?? attributes.ToArray();
        base.ModifyMetadata(metadata,  attrs);

        if (metadata.PropertyName != &amp;quot;icategorizable_category&amp;quot;) return;

        var scaffoldAttribute =
            attrs
            .SafeOfType&amp;lt;ScaffoldColumnAttribute&amp;gt;()
            .FirstOrDefault();
        if (scaffoldAttribute == null) return;

        metadata.ShowForEdit = scaffoldAttribute.Scaffold;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;NOTE: I am using &lt;code&gt;SafeOfType&lt;/code&gt; extension method from the &lt;a href=&quot;https://nuget.episerver.com/package/?id=Geta.Net.Extensions&quot;&gt;Geta.Net.Extensions&lt;/a&gt; library.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now you can use &lt;code&gt;ScaffoldColumn&lt;/code&gt; to control the visibility of the &lt;code&gt;Category&lt;/code&gt; property.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ScaffoldColumn(false)]
public override CategoryList Category { get; set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I am overriding a category property from the base class and applying the attribute.&lt;/p&gt;
</id><updated>2019-08-02T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Promotion exclusion levels in Episerver Commerce</title><link href="http://marisks.net/2019/06/18/promotion-exclusion-levels-in-episerver-commerce/" /><id>&lt;h1 id=&quot;the-issue&quot;&gt;The issue&lt;/h1&gt;
&lt;p&gt;We have item level discounts on two categories where one category has a 30% discount and the second one has a 50% discount. Then we defined an order level discount of 40% when a coupon code is applied. The order of discounts is 50% -&amp;gt; 40% -&amp;gt; 30%.&lt;/p&gt;
&lt;p&gt;When we tried to add products from both categories and then apply a coupon code, we got a validation error that it is an invalid discount combination.&lt;/p&gt;
&lt;h1 id=&quot;the-research-and-the-solution&quot;&gt;The research and the solution&lt;/h1&gt;
&lt;p&gt;Episerver support sent us a &lt;em&gt;Quicksilver&lt;/em&gt; example where this setup worked but with a small difference - 40% discount was an item level discount. We tried to re-create the same setup, but it still did not work.&lt;/p&gt;
&lt;p&gt;After some research, I found that Episerver support changed Quicksilver code, which applies discounts. They have changed the exclusion level from &lt;code&gt;Order&lt;/code&gt; to &lt;code&gt;Unit&lt;/code&gt; when calling &lt;code&gt;ApplyDiscounts&lt;/code&gt; on the cart.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;cart.ApplyDiscounts(_promotionEngine, new PromotionEngineSettings
{
    ExclusionLevel = ExclusionLevel.Unit
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default value for &lt;code&gt;ExclusionLevel&lt;/code&gt; is &lt;code&gt;Order&lt;/code&gt;. So the combination we tried to apply didn&amp;#39;t work. Strangely, this is a default value as such discount combinations are common in e-commerce solutions.&lt;/p&gt;
&lt;p&gt;So if you need control of combining different discounts on item level, always use &lt;code&gt;ExclusionLevel.Unit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Cuong Phan from Episerver support for help!&lt;/p&gt;
</id><updated>2019-06-18T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Episerver: starting a Scheduled Job programmatically</title><link href="http://marisks.net/2019/04/23/episerver-starting-scheduled-job-programmatically/" /><id>&lt;p&gt;While &lt;em&gt;Episerver&amp;#39;s&lt;/em&gt; async methods for starting a scheduled job do not work well, there is a workaround. You can schedule the job to run later. Then the scheduler will pick up the job and start it.&lt;/p&gt;
&lt;p&gt;Here I have created an extension method to schedule the job to run after ten seconds.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static class ScheduledJobExtensions
{
    public static void ScheduleRunNow(
      this ScheduledJob job, IScheduledJobRepository scheduledJobRepository)
    {
        job.IntervalType = ScheduledIntervalType.None;
        job.IntervalLength = 0;
        job.IsEnabled = true;
        job.NextExecution = DateTime.Now.AddSeconds(10);

        scheduledJobRepository.Save(job);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use this extension method like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var job = _scheduledJobRepository.Get(new Guid(MyJob.Guid));
job.ScheduleRunNow(_scheduledJobRepository);
&lt;/code&gt;&lt;/pre&gt;
</id><updated>2019-04-23T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Investigating missing products in content areas</title><link href="http://marisks.net/2019/02/28/investigating-missing-products-in-content-areas/" /><id>&lt;p&gt;After research, we found that it was caused by the import changing content GUIDs on products. It caused other issues too. In this article, I do not want to go into the details of that issue but cover how products are linked in content areas.&lt;/p&gt;
&lt;p&gt;With a few SQL scripts, you can get all you need. The first thing to find out is the ID of the content area property. You can find it in the &lt;code&gt;tblPropertyDefinition&lt;/code&gt; by querying it on &lt;code&gt;fkContentTypeID&lt;/code&gt; column (if you do not know the ID of the content type, find it in the &lt;code&gt;tblContentType&lt;/code&gt; table).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;SELECT *
  FROM [dbo].[tblPropertyDefinition]
  where fkContentTypeID = 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the results, find your property and use it&amp;#39;s &lt;code&gt;pkID&lt;/code&gt; value in the next query.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;tblContentProperty&lt;/code&gt; table, &lt;em&gt;Episerver&lt;/em&gt; stores values of all properties. You can easily get values for all properties by querying by content ID on the &lt;code&gt;fkContentID&lt;/code&gt; column and property definition ID on the &lt;code&gt;fkPropertyDefinitionID&lt;/code&gt; column (the value you get in the previous step). The easiest way to get a content ID is by going into the Episerver edit mode and check the ID under the content properties.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2019-02/content-id-in-edit.jpg&quot; class=&quot;img-responsive&quot; alt=&quot;Content ID in the edit mode.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you get all IDs, run the query. To get values of a content area, you should look into &lt;code&gt;LongString&lt;/code&gt; column.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;SELECT [LongString]
  FROM [dbo].[tblContentProperty]
  where fkPropertyDefinitionID = 800 and fkContentID = 9000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Episerver stores content area data in an &lt;code&gt;XML&lt;/code&gt; format which looks like &lt;code&gt;Html&lt;/code&gt;. Each item in the content area is represented as a &lt;code&gt;DIV&lt;/code&gt; tag with some attributes. I was interested only in two - &lt;code&gt;data-contentguid&lt;/code&gt; and &lt;code&gt;data-contentname&lt;/code&gt;. The first attribute is a GUID which links to the content in the content area and the second helps to identify an item by the name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;&amp;lt;div
    data-classid=&amp;quot;36f4349b-8093-492b-b616-05d8964e4c89&amp;quot;
    data-contentgroup=&amp;quot;&amp;quot;
    data-contentguid=&amp;quot;00000000-0000-1234-0000-000000010000&amp;quot;
    data-contentname=&amp;quot;Fancy stuff&amp;quot;&amp;gt;{}&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our case, product GUIDs were generated in a specific format and those where changing in some cases. In this example, it would be &lt;code&gt;1234&lt;/code&gt; part. Once we found the issues in IDs, we could easily fix it with a script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;UPDATE tblContentProperty
SET LongString = REPLACE(LongString, &amp;#39;00000000-0000-1234-0000-&amp;#39;, &amp;#39;00000000-0000-0000-0000-&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I know that it might be dangerous to modify Episerver DB directly, but we haven&amp;#39;t risked much as products in content areas were not visible anyway. Luckily, this fixed our issues.&lt;/p&gt;
&lt;p&gt;There is another table which might be useful when researching issues with content areas. It is &lt;code&gt;tblContentSoftlink&lt;/code&gt; table.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;SELECT *
  FROM [dbo].[tblContentSoftlink]
  where fkReferencedContentGUID = &amp;#39;00000000-0000-1234-0000-000000010000&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is one important column in this table - &lt;code&gt;ContentLink&lt;/code&gt;. You can find a matching content link for a content GUID in it. In our case, we still saw a content link and it was a valid one - the content existed in the DB. But once we edited content in the edit mode without touching our content area, the content link became empty. The data in the content area still remained (in the &lt;code&gt;tblContentProperty&lt;/code&gt; table).&lt;/p&gt;
</id><updated>2019-02-28T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>EPiServer: working with Scheduled Jobs programmatically. Revisited.</title><link href="http://marisks.net/2019/01/31/episerver-working-with-scheduled-jobs-programmatically-revisited/" /><id>&lt;p&gt;In the latest version, you still have a repository. However, instead of using a class, you should ask for an interface - &lt;em&gt;IScheduledJobRepository&lt;/em&gt;. It still has the same set of methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Get(Guid id):ScheduledJob&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Get(string method, string typeName, string assemblyName):ScheduledJob&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;List():IEnumerable&lt;scheduledjob&gt;&lt;/scheduledjob&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Save(ScheduledJob job):void&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;/2015/05/04/episerver-working-with-scheduled-jobs-programmatically/&quot;&gt;See my previous article for detailed information&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main changes in the API are about executing these jobs. &lt;em&gt;Stop&lt;/em&gt;, &lt;em&gt;ExecuteManually&lt;/em&gt; and other methods of a &lt;em&gt;ScheduledJob&lt;/em&gt; class now are obsolete. You should use &lt;em&gt;IScheduledJobExecutor&lt;/em&gt; instead. &lt;em&gt;IScheduledJobExecutor&lt;/em&gt; has three methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;StartAsync(ScheduledJob job, JobExecutionOptions options, CancellationToken cancellationToken):Task&lt;jobexecutionresult&gt;&lt;/jobexecutionresult&gt;&lt;/em&gt; - starts a job with provided options and cancellation token,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Cancel(Guid id):void&lt;/em&gt; - cancels a job by Id,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ListRunningJobs():IEnumerable&lt;/em&gt; - - returns a list of running jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;IScheduledJobExecutor&lt;/em&gt; also has several extension methods. These extension methods make it much easier to work with scheduled jobs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;StartAsync(this IScheduledJobExecutor executor, ScheduledJob job):Task&lt;jobexecutionresult&gt;&lt;/jobexecutionresult&gt;&lt;/em&gt; - starts a job without any parameters,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;StartAsync(this IScheduledJobExecutor executor, ScheduledJob job, JobExecutionOptions options):Task&lt;jobexecutionresult&gt;&lt;/jobexecutionresult&gt;&lt;/em&gt; - starts a job with options,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ListRunningJobs&amp;lt;T&amp;gt;(this IScheduledJobExecutor executor):IEnumerable&amp;lt;T&amp;gt;&lt;/em&gt; - lists jobs of a specific type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So with all this in your tool belt, you can load a job by id and start it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public async RunMyJob()
{
    var job = _scheduledJobRepository.Get(new Guid(MyJob.Guid));
    if (job.IsRunning)
    {
        return;
    }

    var result = await _scheduledJobExecutor.StartAsync(job);
    if (result.Status == ScheduledJobExecutionStatus.Succeeded)
    {
        return;
    }

    Log.Error($&amp;quot;Failed to start a job: {result.Status} {result.Message}&amp;quot;);
    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You also, can pass some options:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;await _scheduledJobExecutor.StartAsync(job, new JobExecutionOptions
{
    Trigger = ScheduledJobTrigger.User,
    RunSynchronously = true,
    ContentCacheSlidingExpiration = null
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the trigger just indicates how this job was started. It has four values - &lt;em&gt;Unknown&lt;/em&gt;, &lt;em&gt;Scheduler&lt;/em&gt;, &lt;em&gt;User&lt;/em&gt;, &lt;em&gt;Restart&lt;/em&gt;. So you can fake how it was started :)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;RunSynchronously&lt;/em&gt; set to &lt;em&gt;true&lt;/em&gt; will run the job synchronously.&lt;/p&gt;
&lt;p&gt;With &lt;em&gt;ContentCacheSlidingExpiration&lt;/em&gt; you can set how long content is cached when executed.&lt;/p&gt;
</id><updated>2019-01-31T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Refactoring 404 handler to use SQL store</title><link href="http://marisks.net/2018/12/31/refactoring-404-handler-to-use-sql-store/" /><id>&lt;p&gt;If you want to get started with the new version, head over to &lt;a href=&quot;https://getadigital.com/blog/404-handler-with-performance-improvements-released/&quot;&gt;Geta&amp;#39;s blog&lt;/a&gt; for details.&lt;/p&gt;
&lt;h1 id=&quot;refactoring-steps&quot;&gt;Refactoring steps&lt;/h1&gt;
&lt;p&gt;The first challenge I faced was that data access was mixed with some logic in a &lt;code&gt;DataStoreHandler&lt;/code&gt;. However, it was not so easy to extract it out. So I started abstracting away the logic behind the &lt;code&gt;DataStoreHandler&lt;/code&gt; and created an interface for it - &lt;code&gt;IRedirectsService&lt;/code&gt; and implemented it in the &lt;code&gt;DataStoreHandler&lt;/code&gt;. I didn&amp;#39;t like method names in the &lt;code&gt;DataStoreHandler&lt;/code&gt;. So I used different naming in &lt;code&gt;IRedirectsService&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public interface IRedirectsService
{
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetAll();
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetSaved();
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetIgnored();
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetDeleted();
    IEnumerable&amp;lt;CustomRedirect&amp;gt; Search(string searchText);
    void AddOrUpdate(CustomRedirect redirect);
    void AddOrUpdate(IEnumerable&amp;lt;CustomRedirect&amp;gt; redirects);
    void DeleteByOldUrl(string oldUrl);
    int DeleteAll();
    int DeleteAllIgnored();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This new interface works as a facade for all actions that could be done with a redirect.&lt;/p&gt;
&lt;p&gt;The next task was extracting data access. I wanted to separate reads from writes. For writes, I have introduced a generic &lt;code&gt;IRepository&lt;/code&gt; interface. For now, it just supports saving and deleting of requests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public interface IRepository&amp;lt;TEntity&amp;gt;
    where TEntity : class
{
    void Save(TEntity entity);
    void Delete(TEntity entity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For reads, I have created an interface specific for redirect querying - &lt;code&gt;IRedirectLoader&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public interface IRedirectLoader
{
    CustomRedirect GetByOldUrl(string oldUrl);
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetAll();
    IEnumerable&amp;lt;CustomRedirect&amp;gt; GetByState(RedirectState state);
    IEnumerable&amp;lt;CustomRedirect&amp;gt; Find(string searchText);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could use a query pattern here, but it would complicate things. I also wanted to stick to &lt;em&gt;Episerver&lt;/em&gt; naming convention (for example, &lt;code&gt;IContentLoader&lt;/code&gt; for content querying in &lt;em&gt;Episerver&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;With these two interfaces, I could extract out data access. So I have implemented &lt;code&gt;DdsRedirectRepository&lt;/code&gt; by extracting all data access logic from &lt;code&gt;DataStoreHandler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once this was done, it was simple to move the rest of the &lt;code&gt;DataStoreHandler&lt;/code&gt; logic into a separate &lt;code&gt;IRedirectsService&lt;/code&gt; implementation and leave &lt;code&gt;DataStoreHandler&lt;/code&gt; just for backward compatibility (if someone uses it). I have implemented &lt;code&gt;DefaultRedirectsService&lt;/code&gt; which mostly coordinates data access.&lt;/p&gt;
&lt;h1 id=&quot;sql-store&quot;&gt;SQL store&lt;/h1&gt;
&lt;p&gt;As I have finished refactoring, I could start implementing SQL store. The first task was creating a new table and implement upgrade steps. The 404 handler has &lt;code&gt;Upgrader&lt;/code&gt; class which already creates a table for suggestions. I have used it as an example for the redirects table. However, before implementation of it, &lt;code&gt;Upgrader&lt;/code&gt; needed a refactoring. I have extracted all upgrade steps in separate methods so that it is easier to add new steps.&lt;/p&gt;
&lt;p&gt;Once table creation was implemented in the &lt;code&gt;Upgrader&lt;/code&gt;, I could start working on &lt;code&gt;SqlRedirectRepository&lt;/code&gt;. I used &lt;em&gt;Episerver&amp;#39;s&lt;/em&gt; &lt;code&gt;IDatabaseExecutor&lt;/code&gt; to run &lt;em&gt;ADO.NET&lt;/em&gt; queries. For more straightforward data reading I have used &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/queries-in-linq-to-dataset&quot;&gt;Linq to DataSet&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;private IEnumerable&amp;lt;CustomRedirect&amp;gt; ExecuteEnumerableQuery(DbCommand command)
{
    var table = ExecuteDataTableQuery(command);

    return table
        .AsEnumerable()
        .Select(ToCustomRedirect);
}

private DataTable ExecuteDataTableQuery(DbCommand command)
{
    var adapter = _executor.DbFactory.CreateDataAdapter();
    if (adapter == null) throw new Exception(&amp;quot;Unable to create DbDataAdapter&amp;quot;);

    adapter.SelectCommand = command;
    var ds = new DataSet();
    adapter.Fill(ds);
    return ds.Tables[0];
}

private static CustomRedirect ToCustomRedirect(DataRow x)
{
    return new CustomRedirect(
        x.Field&amp;lt;string&amp;gt;(&amp;quot;OldUrl&amp;quot;),
        x.Field&amp;lt;string&amp;gt;(&amp;quot;NewUrl&amp;quot;),
        x.Field&amp;lt;bool&amp;gt;(&amp;quot;WildCardSkipAppend&amp;quot;))
    {
        Id = Identity.NewIdentity(x.Field&amp;lt;Guid&amp;gt;(&amp;quot;Id&amp;quot;)),
        State = x.Field&amp;lt;int&amp;gt;(&amp;quot;State&amp;quot;)
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step was adding data migration from DDS to the SQL store. I have just added an action to the gadget&amp;#39;s controller, then used the &lt;code&gt;DdsRedirectRepository&lt;/code&gt; to read old records and the new &lt;code&gt;IRedirectsService&lt;/code&gt; to save records.&lt;/p&gt;
&lt;h1 id=&quot;performance&quot;&gt;Performance&lt;/h1&gt;
&lt;p&gt;After I have implemented SQL store, I wanted to see how much faster the 404 handler become. I have imported &lt;code&gt;6000&lt;/code&gt; redirects in DDS and SQL stores and tried to load all redirects. The results were fascinating. Loading of all records from DDS store took &lt;code&gt;~16000 ms&lt;/code&gt; while from SQL store it took only &lt;code&gt;26 ms&lt;/code&gt;. It is more than 600 times faster.&lt;/p&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;While it didn&amp;#39;t seem much work implementing support for multiple storages, it took some time to finish it. Ideally, I would write integration tests to make sure that nothing was broken, but it was too hard to implement those to work with &lt;em&gt;Episerver APIs&lt;/em&gt; - DDS and SQL.&lt;/p&gt;
&lt;p&gt;# New Year!&lt;/p&gt;
</id><updated>2018-12-31T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>New version of Geta Tags released</title><link href="http://marisks.net/2018/12/12/new-version-of-geta-tags/" /><id>&lt;p&gt;For more information see &lt;a href=&quot;https://getadigital.com/blog/new-version-of-geta-tags-released/&quot;&gt;Geta&amp;#39;s blog&lt;/a&gt;.&lt;/p&gt;
</id><updated>2018-12-12T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>A complete configuration recipe for Episerver library</title><link href="http://marisks.net/2018/10/30/complete-configuration-recipe-for-episerver-library/" /><id>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I have created an example library to show how the configuration should be set up. The library adds &lt;em&gt;Episerver&lt;/em&gt; content event logging. A library user can configure the log level and to which events to subscribe (it supports two events in an example).&lt;/p&gt;
&lt;h2 id=&quot;library-implementation&quot;&gt;Library implementation&lt;/h2&gt;
&lt;p&gt;First of all, let&amp;#39;s create the functionality of our library. I am creating a class which logs an event using &lt;em&gt;Episerver&lt;/em&gt; logging. It has two parameters - the name of an event and &lt;em&gt;ContentEventArgs&lt;/em&gt; arguments, and it logs the provided information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class ContentEventLogger
{
    private readonly ILogger _logger = LogManager.GetLogger(typeof(ContentEventLogger));

    public virtual void Log(string name, ContentEventArgs args)
    {
        _logger.Log(Level.Information, $&amp;quot;Event: {name}; Content: {args.Content?.Name}&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The requirements stated that we should be able to change the log level by some configuration. For this purpose, I am creating a settings class and inject it in our logger.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class LoggerSettings
{
    public LoggerSettings()
    {
        Level = Level.Information;
    }

    public Level Level { get; private set; }

    public LoggerSettings LogLevel(Level level)
    {
        return new LoggerSettings { Level = level };
    }
}

public class ContentEventLogger
{
    private readonly LoggerSettings _settings;
    private readonly ILogger _logger = LogManager.GetLogger(typeof(ContentEventLogger));

    public ContentEventLogger(LoggerSettings settings)
    {
        _settings = settings ?? throw new ArgumentNullException(nameof(settings));
    }

    public virtual void Log(string name, ContentEventArgs args)
    {
        _logger.Log(_settings.Level, $&amp;quot;Event: {name}; Content: {args.Content?.Name}&amp;quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;library-s-settings-configuration&quot;&gt;Library&amp;#39;s settings configuration&lt;/h2&gt;
&lt;p&gt;Now we provided settings of our logger through dependency injection. However, how to configure it so that settings are injected, and it is simple enough for our library users?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Episerver&lt;/em&gt; has an API for dependency injection configuration. We can use it to register our settings and our logger in an &lt;em&gt;IConfigurableModule&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public void ConfigureContainer(ServiceConfigurationContext context)
{
    var settings = new LoggerSettings();
    context.Services.AddSingleton(settings);
    context.Services.AddSingleton&amp;lt;ContentEventLogger&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this works, the question is who is responsible for this registration. If we create an &lt;em&gt;IConfigurableModule&lt;/em&gt; in our library, then the user will not be able to set the settings they need. If we make the user responsible for all registrations, the user will need proper documentation to not mess up with it. In this example, the configuration is simple, but what would happen if the user has to register ten or more services?&lt;/p&gt;
&lt;p&gt;The solution is in the middle. We can provide a simple API for the user to configure the library in their &lt;em&gt;IConfigurableModule&lt;/em&gt; but same time without too many details.&lt;/p&gt;
&lt;p&gt;For this purpose, I am creating several extension methods for &lt;em&gt;ServiceConfigurationContext&lt;/em&gt;. There is one extension method which uses default configuration and the second one which allows setting log level you want. Notice that I prefixed extension methods with &lt;em&gt;Add&lt;/em&gt;. This approach is a convention used in &lt;em&gt;ASP.NET Core&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static void AddContentEventLogger(
    this ServiceConfigurationContext context)
{
    context.AddContentEventLogger(_ =&amp;gt; _);
}

public static void AddContentEventLogger(
    this ServiceConfigurationContext context,
     Func&amp;lt;LoggerSettings, LoggerSettings&amp;gt; configure)
{
    var settings = configure(new LoggerSettings());

    context.Services.AddSingleton(settings);
    context.Services.AddSingleton&amp;lt;ContentEventLogger&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these extension methods, the user can register our logger in their &lt;em&gt;IConfigurableModule&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public void ConfigureContainer(ServiceConfigurationContext context)
{
    context.AddContentEventLogger();

    // or

    context.AddContentEventLogger(x =&amp;gt; x.LogLevel(Level.Error));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;initialization-configuration&quot;&gt;Initialization configuration&lt;/h2&gt;
&lt;p&gt;While we have implemented our logging functionality, it is not used anywhere. We have to attach it to &lt;em&gt;Episerver&lt;/em&gt; events. So we need some initialization logic.&lt;/p&gt;
&lt;p&gt;Again, we can create an initialization module in our library and attach to the events but this way our library user loses control. They are not able to configure to which events to attach.&lt;/p&gt;
&lt;p&gt;Instead, let&amp;#39;s create a particular class for our library initialization for which we provide additional settings.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public enum ContentEvent
{
    Created,
    Published
}

public class InitializerSettings
{
    public IEnumerable&amp;lt;ContentEvent&amp;gt; Events { get; private set; }

    public InitializerSettings()
    {
        Events = Enumerable.Empty&amp;lt;ContentEvent&amp;gt;();
    }

    public InitializerSettings SubscribeTo(ContentEvent contentEvent)
    {
        return new InitializerSettings
        {
            Events = Events.Union(new [] {contentEvent})
        };
    }
}

public class Initializer
{
    private readonly IContentEvents _contentEvents;
    private readonly ContentEventLogger _logger;

    public Initializer(
        IContentEvents contentEvents,
        ContentEventLogger logger)
    {
        _contentEvents = contentEvents ?? throw new ArgumentNullException(nameof(contentEvents));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public void Initialize(InitializerSettings settings)
    {
        foreach (var contentEvent in settings.Events)
        {
            Subscribe(contentEvent);
        }
    }

    private void Subscribe(ContentEvent contentEvent)
    {
        switch (contentEvent)
        {
            case ContentEvent.Created:
                _contentEvents.CreatedContent += _contentEvents_CreatedContent;
                break;
            case ContentEvent.Published:
                _contentEvents.PublishedContent += _contentEvents_PublishedContent;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(contentEvent), contentEvent, null);
        }
    }

    private void _contentEvents_PublishedContent(object sender, ContentEventArgs e)
    {
        _logger.Log(&amp;quot;Published&amp;quot;, e);
    }

    private void _contentEvents_CreatedContent(object sender, ContentEventArgs e)
    {
        _logger.Log(&amp;quot;Created&amp;quot;, e);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By calling &lt;em&gt;Initialize&lt;/em&gt; and providing settings, we can attach to different (or multiple) events. As with dependency injection configuration, we can add a helper extension method. Notice that for this extension method I used &lt;em&gt;Use&lt;/em&gt; prefix. Also, same as in &lt;em&gt;ASP.NET Core&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static void UseContentEventLogger(
  this InitializationEngine context, Func&amp;lt;InitializerSettings, InitializerSettings&amp;gt; configure)
{
  var settings = configure(new InitializerSettings());

  var initializer = context.Locate.Advanced.GetInstance&amp;lt;Initializer&amp;gt;();
  initializer.Initialize(settings);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Besides, do not forget to register our initializer in a container with &lt;em&gt;AddContentEventLogger&lt;/em&gt; extension method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static void AddContentEventLogger(
  this ServiceConfigurationContext context, Func&amp;lt;LoggerSettings, LoggerSettings&amp;gt; configure)
{
  var settings = configure(new LoggerSettings());

  context.Services.AddSingleton(settings);
  context.Services.AddSingleton&amp;lt;ContentEventLogger&amp;gt;();

  context.Services.AddSingleton&amp;lt;Initializer&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this is done, the user can attach to the different events in their &lt;em&gt;IInitializableModule&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt; public void Initialize(InitializationEngine context)
{
  context.UseContentEventLogger(
      x =&amp;gt; x
          .SubscribeTo(ContentEvent.Created)
          .SubscribeTo(ContentEvent.Published));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Providing configuration options for your library users is important. However, it is also important to have a good API. By following &lt;em&gt;ASP.NET Core&lt;/em&gt; example, we can create nice configuration API for &lt;em&gt;Episerver&lt;/em&gt; too.&lt;/p&gt;
&lt;p&gt;For the full example, check &lt;a href=&quot;https://github.com/marisks/examples/tree/master/ContentEventLogger&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;# configuring!&lt;/p&gt;
</id><updated>2018-10-30T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Fluent configuration API for your Episerver library</title><link href="http://marisks.net/2018/09/14/fluent-configuration-api-for-your-episerver-library/" /><id>&lt;p&gt;In the &lt;a href=&quot;/2018/08/28/configuring-your-epsiserver-libraries-net-core-way/&quot;&gt;previous article&lt;/a&gt;, I have created an extension method for &lt;em&gt;InitializationEngine&lt;/em&gt; to be able to pass configuration settings to my library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public void Initialize(InitializationEngine context)
{
  context.UseMyLibrary(settings =&amp;gt; {
    settings.MyProperty = 20;
    return settings;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The extension method expects a function parameter which will return configured settings. This could be simplified by passing an action method (&lt;code&gt;Action&amp;lt;MySettings&amp;gt;&lt;/code&gt;) instead, but the function was used on purpose. It gives us an option of creating a fluent API. The usage of such API could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public void Initialize(InitializationEngine context)
{
  context.UseMyLibrary(settings =&amp;gt;
    settings
      .WithMyProperty(20)
      .WithAnotherProperty(&amp;quot;Hello&amp;quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two ways how we can build this - with mutable and immutable settings class. There are several benefits of using one over another.&lt;/p&gt;
&lt;h2 id=&quot;mutable-fluent-api&quot;&gt;Mutable fluent API&lt;/h2&gt;
&lt;p&gt;A mutable option is simple. You have to create methods which manipulate the internal state of the object and returns it as a result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class MySettings
{
  public int MyProperty { get; }
  public string AnotherProperty { get; }

  public MySettings()
  {
    MyProperty = 10;
    AnotherProperty = string.Empty;
  }

  public MySettings WithMyProperty(int value)
  {
    MyProperty = value;
    return this;
  }

  public MySettings WithAnotherProperty(string value)
  {
    AnotherProperty = value;
    return this;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;immutable-fluent-api&quot;&gt;Immutable fluent API&lt;/h2&gt;
&lt;p&gt;An immutable fluent API is harder to implement, but it allows for sharing the settings and modifying those independently. It would enable pre-build settings and then create different configurations based on the pre-built.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;var settings =
  new MySettings()
    .WithMyProperty(10);

var settings1 = settings.WithAnotherProperty(&amp;quot;Hello&amp;quot;);
var settings2 = settings.WithAnotherProperty(&amp;quot;world!&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;em&gt;settings1&lt;/em&gt; and &lt;em&gt;settings2&lt;/em&gt; are two different objects with different values. Such a scenario is not possible with mutable settings - there would be only one object which would hold the last value;&lt;/p&gt;
&lt;p&gt;The most straightforward implementation would require you to copy all unchanged values to the new object in each method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class MySettings
{
  public int MyProperty { get; private set; }
  public string AnotherProperty { get; private set; }

  public MySettings()
  {
    MyProperty = 10;
    AnotherProperty = string.Empty;
  }

  public MySettings WithMyProperty(int value)
  {
    return new MySettings { MyProperty = value, AnotherProperty = AnotherProperty };
  }

  public MySettings WithAnotherProperty(string value)
  {
    return new MySettings { MyProperty = MyProperty, AnotherProperty = value };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but with lots of properties, it would be annoying to do copying manually in each method. In F# there is a copy-and-update record expression (&lt;em&gt;with&lt;/em&gt;) for record types (immutable classes).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-fsharp&quot;&gt;let settings = {myProperty=10; anotherProperty=&amp;quot;&amp;quot;}
let settings1 = {settings with anotherProperty=&amp;quot;Hello&amp;quot;}
let settings2 = {settings with anotherProperty=&amp;quot;world!&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In C#, you can create a method which internally could mutate the new object. Then there would be only one place where you should modify copying logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class MySettings
{
  public int MyProperty { get; private set; }
  public string AnotherProperty { get; private set; }

  public MySettings()
  {
    MyProperty = 10;
    AnotherProperty = string.Empty;
  }

  public MySettings WithMyProperty(int value)
  {
    return With(settings =&amp;gt; settings.MyProperty = value);
  }

  public MySettings WithAnotherProperty(string value)
  {
    return With(settings =&amp;gt; settings.AnotherProperty = value);
  }

  private MySettings With(Action&amp;lt;MySettings&amp;gt; mutate)
  {
    var settings = new MySettings
    {
      MyProperty = MyProperty,
      AnotherProperty = AnotherProperty
    };
    mutate(settings);
    return settings;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Fluent APIs give us simple, easy discoverable APIs. Those can be useful when configuring your application or library. You can implement those as mutable or immutable. Mutable APIs are simpler to implement but has trouble with sharing. Immutable APIs are a little bit harder to implement but are more flexible.&lt;/p&gt;
&lt;p&gt;So for simple scenarios with few settings, it would be okay to use a mutable API. When you have lots of properties which you need to share and create different configurations for different cases, use immutable solution.&lt;/p&gt;
</id><updated>2018-09-14T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Configuring your Episerver libraries .NET Core way</title><link href="http://marisks.net/2018/08/28/configuring-your-epsiserver-libraries-net-core-way/" /><id>&lt;h1 id=&quot;drawbacks-of-initializable-modules-and-configuration-settings&quot;&gt;Drawbacks of initializable modules and configuration settings&lt;/h1&gt;
&lt;p&gt;The main drawback of initializable modules in libraries is that a library user cannot control when an initializable module is called. The user might want to disable initialization code too.&lt;/p&gt;
&lt;p&gt;Another drawback is the startup time. Episerver scans for initializable modules, and it will take slightly more time to find and run those. If each small library would have an initializable module, then a more significant project which uses several libraries might be slow at startup.&lt;/p&gt;
&lt;p&gt;Putting configuration settings in the Web.config forces to use only one way of configuration. If a developer wants to retrieve a configuration from another source (for example, database), then there is no way to do it. Also, in the future when we will get to .NET Core in Episerver, the library which forces usage of Web.config will not work anymore.&lt;/p&gt;
&lt;h1 id=&quot;the-new-way&quot;&gt;The new way&lt;/h1&gt;
&lt;p&gt;First of all, the configuration should be done in the code. This allows a developer to choose the source of configuration. In the simplest form, it is hardcoded in the code.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ASP.NET Core&lt;/em&gt; has an excellent example of how to achieve it. It uses &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1#the-configureservices-method&quot;&gt;ConfigureServices&lt;/a&gt; method for IoC configuration and &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1#the-configure-method&quot;&gt;Configure&lt;/a&gt; method for request handling (and other) configuration.&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;Episerver&lt;/em&gt;, we have two similar extension points - &lt;a href=&quot;/link/ebeb7390a6de4c16a2530221b226e292.aspx&quot;&gt;IConfigurableModule&lt;/a&gt; for IoC configuration and &lt;a href=&quot;/link/be4f88d913c443549fcc389dc543fc42.aspx&quot;&gt;IInitializableModule&lt;/a&gt; for other initialization code. Previously, we used to create own &lt;em&gt;IConfigurableModule&lt;/em&gt; and &lt;em&gt;IInitializableModule&lt;/em&gt; in our libraries, but instead, we should allow our library users to call library configuration code in their modules.&lt;/p&gt;
&lt;p&gt;The usage of an imaginable library&amp;#39;s - &lt;em&gt;MyLibrary&lt;/em&gt; configuration might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[InitializableModule]
public class IoCModule : IConfigurableModule
{
  public void ConfigureContainer(ServiceConfigurationContext context)
  {
    context.AddMyLibrary();
  }
  public void Initialize(InitializationEngine context)
  {
  }
  public void Uninitialize(InitializationEngine context)
  {
  }
}

[InitializableModule]
[ModuleDependency(
  typeof(EPiServer.Web.InitializationModule),
  typeof(EPiServer.Commerce.Initialization.InitializationModule))]
public class AppInitialization : IInitializableModule
{
  public void Initialize(InitializationEngine context)
  {
    context.UseMyLibrary(settings =&amp;gt; {
      settings.MyProperty = 20;
      return settings;
    });
  }

  public void Uninitialize(InitializationEngine context)
  {
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I am using the same naming as in &lt;em&gt;ASP.NET Core&lt;/em&gt; - &lt;em&gt;IoC&lt;/em&gt; configuration extension methods start with &lt;em&gt;Add&lt;/em&gt; and configuration methods in an initializable module start with &lt;em&gt;Use&lt;/em&gt;. In the initializable module, you will have an option to configure the library by passing an action with settings configuration as a parameter.&lt;/p&gt;
&lt;p&gt;An extension for IoC configuration is simple. It just registers your classes in an IoC container.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static class IoCExtensions
{
  public static void AddMyLibrary(this ServiceConfigurationContext context)
  {
    context.Services.AddTransient&amp;lt;IService, Service&amp;gt;();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An extension with configuration is a little bit more complicated. First of all, you will need a settings class. You can add default settings in the constructor.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class MySettings
{
  public MySettings()
  {
    MyProperty = 10;
  }
  public int MyProperty { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, you need a class as an entry point for your library initialization. This class is responsible for passing settings to an appropriate destination. It could be some context class, storing in a database, passing settings to the service classes, etc.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public class MyLibraryInitializer
{
  IService _service;

  public MyLibraryInitializer(IService service, IService2 service2 ...)
  {
    _service = service;
  }

  public void Initialize(MySettings settings)
  {
    _service.AddMyProperty(settings.MyProperty);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can create an extension. This extension is just responsible for passing configured settings to the initializer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;public static class InitializationExtensions
{
  public static void UseMyLibrary(this this InitializationEngine context, Func&amp;lt;MySettings, MySettings&amp;gt; configure)
  {
    var settings = new MySettings();
    settings = configure(settings);

    var initializer = context.Locate.Advanced.GetInstance&amp;lt;MyLibraryInitializer&amp;gt;();
    initializer.Initialize(settings);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now with this approach developers are free to choose how and when to call the initialization code of your library.&lt;/p&gt;
</id><updated>2018-08-28T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Finding content type conflict reasons with Episerver Developer tools</title><link href="http://marisks.net/2018/07/16/finding-content-type-conflict-reasons-with-dev-tools/" /><id>&lt;p&gt;After writing the previous article about this topic, I have created a pull request to &lt;em&gt;Episerver Developer Tools&lt;/em&gt; with the changes required to solve this issue. I have made some changes though. I have added support for ACL conflicts. Now when using &lt;em&gt;Episerver Developer Tools&lt;/em&gt;,  you will be able to see those too.&lt;/p&gt;
&lt;p&gt;You can see content type conflicts in the &lt;em&gt;Developer tools&lt;/em&gt; under &lt;em&gt;Content Type Analyzer&lt;/em&gt; section. There is a separate column - &lt;em&gt;Conflicts&lt;/em&gt; where you will find conflict details.&lt;/p&gt;
&lt;p&gt;Here is an example of how those will be displayed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-07/content-type-conflicts.png&quot; class=&quot;img-responsive&quot; alt=&quot;Content type conflicts&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Field order&lt;/em&gt; here is a conflicted property name. Then you will see the value defined in the code and a database.&lt;/p&gt;
&lt;p&gt;Below is a list of conflicts it displays.&lt;/p&gt;
&lt;p&gt;For content types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model type&lt;/li&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;Display name&lt;/li&gt;
&lt;li&gt;Sort order&lt;/li&gt;
&lt;li&gt;GUID&lt;/li&gt;
&lt;li&gt;Availability&lt;/li&gt;
&lt;li&gt;ACL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For property types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tab name&lt;/li&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Description (help text)&lt;/li&gt;
&lt;li&gt;Display name (edit caption)&lt;/li&gt;
&lt;li&gt;Culture specific (language specific)&lt;/li&gt;
&lt;li&gt;Required&lt;/li&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Available in edit mode (display edit UI)&lt;/li&gt;
&lt;li&gt;Field order&lt;/li&gt;
&lt;/ul&gt;
</id><updated>2018-07-16T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Dangerous scheduled jobs</title><link href="http://marisks.net/2018/06/12/dangerous-scheduled-jobs/" /><id>&lt;p&gt;Our project was throwing exceptions related to database deadlocks and other concurrency issues. However, this was occasionally happening during the import.&lt;/p&gt;
&lt;p&gt;The first thing I thought, that maybe there is an issue with multiple Azure website instances. I checked if two instances are running the same job in parallel. However, it seemed that those weren&amp;#39;t.&lt;/p&gt;
&lt;p&gt;Recently, we were migrating the project to the DXC and were copying blobs to the DXC. We have stopped all scheduled jobs which could update blobs. Then we have noticed that blobs were still changing as if import job is running. I have checked the jobs, and all were disabled in admin UI. Then checked those in a database and found that there are multiple records with the same jobs. The only difference was a display name.&lt;/p&gt;
&lt;p&gt;As I understand, it happened when we have changed the display name of the scheduled job. &lt;em&gt;Episerver&lt;/em&gt; created a new record and didn&amp;#39;t remove the old one. I have checked with other colleagues that they also have this issue in &lt;em&gt;Episerver CMS 11&lt;/em&gt;, but it doesn&amp;#39;t appear in &lt;em&gt;CMS 10&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You can omit this issue if you set &lt;em&gt;GUID&lt;/em&gt; of your scheduled job:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[ScheduledPlugIn(DisplayName = &amp;quot;ScheduledJobExample&amp;quot;, GUID = &amp;quot;d6619008-3e76-4886-b3c7-9a025a0c2603&amp;quot;)]
public class ScheduledJobExample : ScheduledJobBase
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You also should check if in your current database there are job duplicates. The easiest way is to sort the jobs by the type name in a SQL query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;SELECT *
  FROM [dbo].[tblScheduledItem]
  ORDER BY TypeName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have noticed that even &lt;em&gt;Episerver&lt;/em&gt; scheduled jobs are duplicated.&lt;/p&gt;
&lt;p&gt;Once you found all your duplicated jobs, you can remove those with a script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-sql&quot;&gt;DECLARE @id AS uniqueidentifier = &amp;#39;3134D210-6971-4DF4-8662-FB4E67E41B80&amp;#39;

DELETE [dbo].[tblScheduledItemLog] WHERE [fkScheduledItemId] = @id
DELETE [dbo].[tblScheduledItem] WHERE [pkID] = @id
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;This was a nasty issue we haven&amp;#39;t noticed for a long time. It could be omitted if &lt;em&gt;GUID&lt;/em&gt; field would be made mandatory by &lt;em&gt;Episerver&lt;/em&gt;. Also, &lt;em&gt;Episerver&lt;/em&gt; should handle duplicate jobs - remove which doesn&amp;#39;t match the job definition in code.&lt;/p&gt;
</id><updated>2018-06-12T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Episerver Commerce 11 to 12 upgrade market breaking changes</title><link href="http://marisks.net/2018/05/31/commerce-11-to-12-upgrade-order-market-changes/" /><id>&lt;p&gt;Episerver has made the &lt;em&gt;Market&lt;/em&gt; property on &lt;em&gt;IOrderGroup&lt;/em&gt; obsolete. If you are using it, you will see this warning message:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-text&quot;&gt;&amp;#39;IOrderGroup.Market&amp;#39; is obsolete: &amp;#39;This property is no longer used. Use IMarketService to get the market from MarketId instead. Will remain at least until May 2019.&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, you are tempted to ignore it and fix later as this is just obsolete. You expect it to work as before for some time. However, this is a breaking change you will notice in runtime. The &lt;em&gt;Market&lt;/em&gt; property will be null.&lt;/p&gt;
&lt;p&gt;So fix all warnings related to this property and use &lt;em&gt;IMarketService&lt;/em&gt; to get the market you need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;private readonly IMarketService _marketService;

public ProductController(IMarketService marketService)
{
    _marketService = marketService;
}

private void Run(ICart cart)
{
    var market = _marketService.GetMarket(cart.MarketId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Episerver has documented all breaking changes here: &lt;a href=&quot;/link/900665308fdf4e1ab4ade839886c3af2.aspx&quot;&gt;Breaking changes in Commerce 12&lt;/a&gt;.&lt;/p&gt;
</id><updated>2018-05-31T02:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>