<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Ynze Nunnink</title> <link>https://world.optimizely.com/blogs/ynze-nunnink/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>How to add a custom property in Optimizely Graph</title>            <link>https://blog.ynzen.com/how-to-add-a-custom-property-in-optimizely-graph</link>            <description>&lt;p&gt;In the Optimizely CMS content can be synchronized to the Optimizely Graph service for it then to be exposed by the GraphQL API. In some cases, you may want to add or adjust a property when the content is being synchronized.&lt;/p&gt;&lt;p&gt;An example of this is when media files have a file size property that represents the file size as the number of bytes. The UI that displays this information will need to format the bytes (e.g. &lt;code&gt;10000000&lt;/code&gt;) into a readable string such as &lt;code&gt;10MB&lt;/code&gt;. The server can take on this responsibility by adding another property to the Graph schema that will display the formatted string, which ensures consistency across the applications that require the formatted file size.&lt;/p&gt;&lt;p&gt;The code below will add a custom API model property to the Graph schema called &lt;code&gt;FileSizeFormatted&lt;/code&gt;, which uses the &lt;code&gt;GetValue&lt;/code&gt; method to get the &lt;code&gt;FileSize&lt;/code&gt; property and format it to a readable string using the custom extension method &lt;code&gt;ToFormattedString()&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;ServiceConfiguration(typeof(IContentApiModelProperty), Lifecycle = ServiceInstanceScope.Singleton)&lt;/span&gt;]&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;FileSizeContentApiModelProperty&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IContentApiModelProperty&lt;/span&gt;{    &lt;span class=&quot;hljs-comment&quot;&gt;//To override an existing property you can use the same name.&lt;/span&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; Name =&amp;gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;FileSizeFormatted&quot;&lt;/span&gt;;    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;GetValue&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ContentApiModel contentApiModel&lt;/span&gt;)&lt;/span&gt;    {        &lt;span class=&quot;hljs-comment&quot;&gt;//Validate that the model contains a FileSize property&lt;/span&gt;        &lt;span class=&quot;hljs-comment&quot;&gt;//Another option is to validate the content type&lt;/span&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (!contentApiModel.Properties.TryGetValue(&lt;span class=&quot;hljs-string&quot;&gt;&quot;FileSize&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-keyword&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; property))            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;.Empty;        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;int&lt;/span&gt;.TryParse(property.ToString(), &lt;span class=&quot;hljs-keyword&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; fileSize))            &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; fileSize.ToFormattedString(); &lt;span class=&quot;hljs-comment&quot;&gt;//Custom extension method&lt;/span&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;.Empty;    }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then after running the &lt;strong&gt;content synchronization scheduled job&lt;/strong&gt;, the GraphQL schema and content has been updated with the new property.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-graphql&quot;&gt;{  PdfFile(&lt;span class=&quot;hljs-symbol&quot;&gt;limit:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;100&lt;/span&gt;) {    items {       FileSize       FileSizeFormatted    }  }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;{  &lt;span class=&quot;hljs-attr&quot;&gt;&quot;data&quot;&lt;/span&gt;: {    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;PdfFile&quot;&lt;/span&gt;: {      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;items&quot;&lt;/span&gt;: [        {          &lt;span class=&quot;hljs-attr&quot;&gt;&quot;FileSize&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;2107621&lt;/span&gt;,          &lt;span class=&quot;hljs-attr&quot;&gt;&quot;FileSizeFormatted&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;2mb&quot;&lt;/span&gt;        },        {          &lt;span class=&quot;hljs-attr&quot;&gt;&quot;FileSize&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-number&quot;&gt;1070229&lt;/span&gt;,          &lt;span class=&quot;hljs-attr&quot;&gt;&quot;FileSizeFormatted&quot;&lt;/span&gt;: &lt;span class=&quot;hljs-string&quot;&gt;&quot;1045kb&quot;&lt;/span&gt;        }      ]    }  }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a basic example of creating a custom API model property, and there is room for improvement, because any custom property is added to all schema types. It&#39;s still early days for Optimizely Graph, so we can expect that property customization will be improved in the future.&lt;/p&gt;</description>            <guid>https://blog.ynzen.com/how-to-add-a-custom-property-in-optimizely-graph</guid>            <pubDate>Thu, 09 May 2024 00:29:14 GMT</pubDate>           <category>Blog post</category></item><item> <title>Next level content delivery with Optimizely Graph</title>            <link>https://www.luminary.com/blog/next-level-content-delivery-with-optimizely-graph</link>            <description>Optimizely introduced a new product called Optimizely Graph earlier this year. We were one of the first partners to adopt this new service in a large-scale web development project. Let’s take a look at what Graph brings to the table and why I call it the next level for content delivery.</description>            <guid>https://www.luminary.com/blog/next-level-content-delivery-with-optimizely-graph</guid>            <pubDate>Mon, 02 Oct 2023 13:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Translating content blocks recursively in Optimizely CMS</title>            <link>https://blog.ynzen.com/translating-content-blocks-recursively-in-optimizely-cms</link>            <description>&lt;p&gt;Translating a lot of pages in Optimizely can become a real chore for content editors because pages generally have a lot of child content in the form of blocks. To make things more complicated, blocks can recursively also have child blocks. An average page can easily have 10 or more blocks depending on the amount of content that is displayed.&lt;/p&gt;
&lt;p&gt;By default, blocks have to be translated individually. This means that translating a page will require the editor to do a lot of clicking to translate the content. Luckily there is a sneaky configuration that makes it a lot quicker.&lt;/p&gt;
&lt;h2 id=&quot;heading-the-language-manager&quot;&gt;The Language Manager&lt;/h2&gt;
&lt;p&gt;The language manager can be installed as a NuGet package that does not come with the base installation of Optimizely CMS. It&#39;s very useful for managing the language variations for your content. There are options available for auto-translating and duplicating content into other languages, which by default does not affect child content.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://support.optimizely.com/hc/en-us/articles/4413199657741-Optimizely-Languages-app&quot;&gt;Optimizely Languages app  Support Help Center&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;heading-translating-content&quot;&gt;Translating content&lt;/h3&gt;
&lt;p&gt;To give an example of how the translation works by default, I will use the Language Manager to duplicate content from one language to another on a page that has blocks inside it.&lt;/p&gt;
&lt;p&gt;I&#39;m also using the &lt;a target=&quot;_blank&quot; href=&quot;https://docs.developers.optimizely.com/content-management-system/docs/projects&quot;&gt;projects feature&lt;/a&gt; to get a clear view of what content is being changed. I recommend using projects when translating content because it can also be used to export and import translation (XLIFF) files.&lt;/p&gt;
&lt;p&gt;I&#39;ve created a page called &#39;About us&#39; and added some blocks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313586392/95ef3c67-e2c7-45be-804e-383cf0861600.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &#39;Accordion - about us&#39; block also has some child blocks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313627248/c245a319-4828-4a14-9b9e-70c9138a0fee.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then I use the &#39;Duplicate content&#39; feature to translate the &#39;About us&#39; page to Japanese.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689308778105/b23f2e2b-0e62-4cf0-ab63-c439c0d6d6fe.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I look into my project, I can see that only the About page itself has been created in the Japanese language, while the blocks inside it are still only available in the Australian language.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313896097/c11d96b3-4ea2-4858-a912-e0002798f055.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, I&#39;ll show you how to configure the Language Manager to automatically include child blocks recursively in the translations.&lt;/p&gt;
&lt;h2 id=&quot;heading-configuring-the-content-types-that-include-child-content&quot;&gt;Configuring the content types that include child content&lt;/h2&gt;
&lt;p&gt;There is a somewhat hidden configuration that will make the Language Manager include blocks recursively using some of its translation features. To do this, we just need to configure the content types that should be checked for child blocks when being translated. That can be done in either the &lt;code&gt;appsettings.json&lt;/code&gt; or &lt;code&gt;startup.cs&lt;/code&gt; files, as can be seen in the examples below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Appsettings.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;{  
    &lt;span class=&quot;hljs-attr&quot;&gt;&quot;Episerver&quot;&lt;/span&gt;: {  
        &lt;span class=&quot;hljs-attr&quot;&gt;&quot;CmsUI&quot;&lt;/span&gt;: {  
            &lt;span class=&quot;hljs-attr&quot;&gt;&quot;LanguageManager&quot;&lt;/span&gt;: {  
                &lt;span class=&quot;hljs-attr&quot;&gt;&quot;TranslateOrCopyContentAreaChildrenBlockForTypes&quot;&lt;/span&gt; : [  
                  &lt;span class=&quot;hljs-string&quot;&gt;&quot;AlloyTemplates.Models.Pages.ExamplePage1&quot;&lt;/span&gt;,  
                  &lt;span class=&quot;hljs-string&quot;&gt;&quot;AlloyTemplates.Models.Pages.ExamplePage2&quot;&lt;/span&gt;  
                ]  
            }  
        }  
    }  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Startup.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; EPiServer.Labs.LanguageManager;

&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;
{
    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;
    {
        services.Configure&amp;lt;LanguageManagerOptions&amp;gt;(o =&amp;gt;
        {
            o.TranslateOrCopyContentAreaChildrenBlockForTypes.Add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;AlloyTemplates.Models.Pages.ExamplePage1&quot;&lt;/span&gt;);
            o.TranslateOrCopyContentAreaChildrenBlockForTypes.Add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;AlloyTemplates.Models.Pages.ExamplePage2&quot;&lt;/span&gt;);
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the time you will only need to do this for page types. Note that you need to include the entire namespace path, plus the name of the class.&lt;/p&gt;
&lt;h3 id=&quot;heading-the-result&quot;&gt;The result&lt;/h3&gt;
&lt;p&gt;The result is that when I duplicate the content using the Language Manager, as I&#39;ve shown earlier in this article, the content of the blocks is automatically created in the target language.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689314167401/beddf218-2e8b-49f6-a344-e157bb5f63cd.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Something to note is that if your block is published in the language that you&#39;re copying from, then the block will also be published automatically in the new language. It will not automatically publish your pages, which means you can still review the content fully before publishing it on the site.&lt;/p&gt;
&lt;p&gt;When I was asked if there was an easier way to bulk translate content, I thought that there wasn&#39;t going to be an easy approach. Luckily I found the Language Manager configuration that I had never heard about, even though it is in the documentation &lt;a target=&quot;_blank&quot; href=&quot;https://docs.developers.optimizely.com/content-management-system/reference/optimizely-languages#configure-translate-children-blocks&quot;&gt;here&lt;/a&gt;. It&#39;s easy to miss, so I figured I should share it here!&lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/translating-content-blocks-recursively-in-optimizely-cms</guid>            <pubDate>Fri, 14 Jul 2023 06:26:32 GMT</pubDate>           <category>Blog post</category></item><item> <title>Translating content blocks recursively in Optimizely CMS</title>            <link>https://ynze.hashnode.dev/translating-content-blocks-recursively-in-optimizely-cms</link>            <description>&lt;p&gt;Translating a lot of pages in Optimizely can become a real chore for content editors because pages generally have a lot of child content in the form of blocks. To make things more complicated, blocks can recursively also have child blocks. An average page can easily have 10 or more blocks depending on the amount of content that is displayed.&lt;/p&gt;&lt;p&gt;By default, blocks have to be translated individually. This means that translating a page will require the editor to do a lot of clicking to translate the content. Luckily there is a sneaky configuration that makes it a lot quicker.&lt;/p&gt;&lt;h2 id=&quot;heading-the-language-manager&quot;&gt;The Language Manager&lt;/h2&gt;&lt;p&gt;The language manager can be installed as a &lt;a target=&quot;_blank&quot; href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.Labs.LanguageManager&quot;&gt;NuGet package&lt;/a&gt; that does not come with the base installation of Optimizely CMS. It&#39;s very useful for managing the language variations for your content. There are options available for auto-translating and duplicating content into other languages, which by default does not affect child content.&lt;/p&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://support.optimizely.com/hc/en-us/articles/4413199657741-Optimizely-Languages-app&quot;&gt;Optimizely Languages app  Support Help Center&lt;/a&gt;&lt;/p&gt;&lt;h3 id=&quot;heading-translating-content&quot;&gt;Translating content&lt;/h3&gt;&lt;p&gt;To give an example of how the translation works by default, I will use the Language Manager to duplicate content from one language to another on a page that has blocks inside it.&lt;/p&gt;&lt;p&gt;I&#39;m also using the &lt;a target=&quot;_blank&quot; href=&quot;https://docs.developers.optimizely.com/content-management-system/docs/projects&quot;&gt;projects feature&lt;/a&gt; to get a clear view of what content is being changed. I recommend using projects when translating content because it can also be used to export and import translation (XLIFF) files.&lt;/p&gt;&lt;p&gt;I&#39;ve created a page called &#39;About us&#39; and added some blocks.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313586392/95ef3c67-e2c7-45be-804e-383cf0861600.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;p&gt;The &#39;Accordion - about us&#39; block also has some child blocks.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313627248/c245a319-4828-4a14-9b9e-70c9138a0fee.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;p&gt;Then I use the &#39;Duplicate content&#39; feature to translate the &#39;About us&#39; page to Japanese.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689308778105/b23f2e2b-0e62-4cf0-ab63-c439c0d6d6fe.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;p&gt;When I look into my project, I can see that only the About page itself has been created in the Japanese language, while the blocks inside it are still only available in the Australian language.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689313896097/c11d96b3-4ea2-4858-a912-e0002798f055.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;p&gt;Next, I&#39;ll show you how to configure the Language Manager to automatically include child blocks recursively in the translations.&lt;/p&gt;&lt;h2 id=&quot;heading-configuring-the-content-types-that-should-include-blocks&quot;&gt;Configuring the content types that should include blocks&lt;/h2&gt;&lt;p&gt;There is a somewhat hidden configuration that will make the Language Manager include blocks recursively using some of its translation features. To do this, we just need to configure the content types that should be checked for child blocks when being translated. That can be done in either the &lt;code&gt;AppSettings.json&lt;/code&gt; or &lt;code&gt;Startup.cs&lt;/code&gt; files, as can be seen in the examples below.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;AppSettings.json&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-json&quot;&gt;{      &lt;span class=&quot;hljs-attr&quot;&gt;&quot;Episerver&quot;&lt;/span&gt;: {          &lt;span class=&quot;hljs-attr&quot;&gt;&quot;CmsUI&quot;&lt;/span&gt;: {              &lt;span class=&quot;hljs-attr&quot;&gt;&quot;LanguageManager&quot;&lt;/span&gt;: {                  &lt;span class=&quot;hljs-attr&quot;&gt;&quot;TranslateOrCopyContentAreaChildrenBlockForTypes&quot;&lt;/span&gt; : [                    &lt;span class=&quot;hljs-string&quot;&gt;&quot;MyProject.Models.Pages.StandardPage&quot;&lt;/span&gt;,                    &lt;span class=&quot;hljs-string&quot;&gt;&quot;MyProject.Models.Pages.HomePage&quot;&lt;/span&gt;                  ]              }          }      }  }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Startup.cs&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;using&lt;/span&gt; EPiServer.Labs.LanguageManager;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Startup&lt;/span&gt;{    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ConfigureServices&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;IServiceCollection services&lt;/span&gt;)&lt;/span&gt;    {        services.Configure&amp;lt;LanguageManagerOptions&amp;gt;(o =&amp;gt;        {            o.TranslateOrCopyContentAreaChildrenBlockForTypes.Add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;MyProject.Models.Pages.StandardPage&quot;&lt;/span&gt;);            o.TranslateOrCopyContentAreaChildrenBlockForTypes.Add(&lt;span class=&quot;hljs-string&quot;&gt;&quot;MyProject.Models.Pages.HomePage&quot;&lt;/span&gt;);        });    }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of the time you will only need to do this for page types. Note that you need to include the entire namespace path, plus the name of the class.&lt;/p&gt;&lt;h3 id=&quot;heading-the-result&quot;&gt;The result&lt;/h3&gt;&lt;p&gt;The result is that when I duplicate the content using the Language Manager, as I&#39;ve shown earlier in this article, the content of the blocks is automatically created in the target language.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1689314167401/beddf218-2e8b-49f6-a344-e157bb5f63cd.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;p&gt;Something to note is that if your block is published in the language that you&#39;re copying from, then the block will also be published automatically in the new language. It will not automatically publish your pages, which means you can still review the content fully before publishing it on the site.&lt;/p&gt;&lt;p&gt;When I was asked if there was an easier way to bulk translate content, I thought that there wasn&#39;t going to be an easy approach. Luckily I found the Language Manager configuration that I had never heard about, even though it is in the documentation &lt;a target=&quot;_blank&quot; href=&quot;https://docs.developers.optimizely.com/content-management-system/reference/optimizely-languages#configure-translate-children-blocks&quot;&gt;here&lt;/a&gt;. It&#39;s easy to miss, so I figured I should share it here!&lt;/p&gt;</description>            <guid>https://ynze.hashnode.dev/translating-content-blocks-recursively-in-optimizely-cms</guid>            <pubDate>Fri, 14 Jul 2023 06:26:32 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely named a leader in the Gartner 2023 Magic Quadrant</title>            <link>https://www.luminary.com/blog/optimizely-named-a-leader-in-the-gartner-2023-magic-quadrant</link>            <description>The Optimizely DXP and CMP are once again being recognised as leaders in the Magic Quadrant by Gartner. </description>            <guid>https://www.luminary.com/blog/optimizely-named-a-leader-in-the-gartner-2023-magic-quadrant</guid>            <pubDate>Mon, 20 Mar 2023 01:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely as a headless CMS</title>            <link>https://www.luminary.com/blog/optimizely-as-a-headless-cms</link>            <description>The Optimizely CMS has always been known as a traditional CMS, but is this still the case? Newly added features are pushing it into headless territory, and they come with some competitive advantages. </description>            <guid>https://www.luminary.com/blog/optimizely-as-a-headless-cms</guid>            <pubDate>Thu, 16 Mar 2023 13:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>How to use Optimizely to personalise your website</title>            <link>https://www.luminary.com/blog/how-to-use-optimizely-to-personalise-your-website</link>            <description>Optimizely has many personalisation capabilities, and it can be challenging to understand how they all work. Let&#39;s take a look at some of the personalisation solutions and how they operate.</description>            <guid>https://www.luminary.com/blog/how-to-use-optimizely-to-personalise-your-website</guid>            <pubDate>Mon, 27 Feb 2023 13:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>What’s next after Google Optimize’s sunsetting?</title>            <link>https://www.luminary.com/blog/what-s-next-after-google-optimize-s-sunsetting</link>            <description>Google has announced that it is sunsetting the Google Optimize and Optimize 360 services, forcing customers to explore new platforms and invest in transitioning before September 2023.</description>            <guid>https://www.luminary.com/blog/what-s-next-after-google-optimize-s-sunsetting</guid>            <pubDate>Tue, 31 Jan 2023 02:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely multi-site best practices: a presentation by MVP Ynze Nunnink</title>            <link>https://www.luminary.com/blog/optimizely-multi-site-best-practices-a-presentation-by-mvp-ynze-nunnink</link>            <description>A showcase of Optimizely multi-site best practices, featuring Daimler sub-brands Fuso and Freightliner. </description>            <guid>https://www.luminary.com/blog/optimizely-multi-site-best-practices-a-presentation-by-mvp-ynze-nunnink</guid>            <pubDate>Sun, 09 Oct 2022 13:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely names Luminary Senior Developer, Ynze Nunnink, OMVP </title>            <link>https://www.luminary.com/blog/optimizely-names-luminary-senior-developer-ynze-nunnink-omvp</link>            <description>Luminary Senior Developer and Optimizely Lead, Ynze Nunnink has secured the coveted position of Optimizely MVP. Earning a Platinum badge for his contribution to the Optimizely and greater CMS community, Ynze will continue to share knowledge and enhance the Optimizely product offering through his work.</description>            <guid>https://www.luminary.com/blog/optimizely-names-luminary-senior-developer-ynze-nunnink-omvp</guid>            <pubDate>Sun, 02 Oct 2022 13:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>A multi-brand strategy in Optimizely DXP</title>            <link>https://www.luminary.com/blog/a-multi-brand-strategy-in-optimizely-dxp</link>            <description>The Optimizely Digital Experience Platform (DXP) is ideally suited to digital projects featuring multiple websites or applications. Here, we showcase a multi-brand engagement we are undertaking for Daimler.</description>            <guid>https://www.luminary.com/blog/a-multi-brand-strategy-in-optimizely-dxp</guid>            <pubDate>Thu, 29 Sep 2022 14:00:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely community meetup - Sept 29 (virtual + Melbourne)</title>            <link>https://blog.ynzen.com/optimizely-community-meetup-sept-29</link>            <description>&lt;p&gt;Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP&#39;s see the official Meetup page:
&lt;a target=&quot;_blank&quot; href=&quot;https://www.meetup.com/optimizely-melbourne/events/288300244/&quot;&gt;https://www.meetup.com/optimizely-melbourne/events/288300244/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-meetup-details&quot;&gt;Meetup details&lt;/h2&gt;
&lt;p&gt;Luminary and Optimizely are warmly welcoming back the Optimizely community with a mega meetup, including three great in-person talks (remote enabled). Hear about the latest platform developments as well as seeing some truly incredible projects in action.&lt;/p&gt;
&lt;p&gt;This is an event for the entire Optimizely developer and user community.
There will be three 20-minute short talks, each with social breaks in between:&lt;/p&gt;
&lt;p&gt;A showcase of Optimizely multi-site best-practices, featuring Fuso Trucks from Ynze Nunnink, Luminary Optimizely Lead &amp;amp; Optimizely MVP
The evolution of personalisation  Nicola Ayan, Director of Technology and Growth, Optimizely APJ
Unveiling of exciting new developments from Optimizely (plus two books to give away!)
For those attending the event in person, Luminary is catering with substantial food and premium drink options.&lt;/p&gt;
&lt;p&gt;The three best questions from guests (as voted by our speakers) will each receive a $100 Prezzee Gift Card, redeemable at more than 250 major retailers Australia-wide.&lt;/p&gt;
&lt;p&gt;We are so excited to share this event with you!&lt;/p&gt;
&lt;h2 id=&quot;heading-how-to-join-online&quot;&gt;How to join online?&lt;/h2&gt;
&lt;p&gt;Keep an eye on the &lt;a target=&quot;_blank&quot; href=&quot;https://www.meetup.com/optimizely-melbourne/events/288300244/&quot;&gt;meetup page&lt;/a&gt; for the latest details on how to join. It will be a zoom meeting and you will be able to communicate and ask questions!&lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/optimizely-community-meetup-sept-29</guid>            <pubDate>Tue, 27 Sep 2022 04:48:08 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely community meetup - Sept 29 (virtual + Melbourne)</title>            <link>https://ynze.hashnode.dev/optimizely-community-meetup-sept-29</link>            <description>&lt;p&gt;Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP&#39;s see the official Meetup page:&lt;a target=&quot;_blank&quot; href=&quot;https://www.meetup.com/optimizely-melbourne/events/288300244/&quot;&gt;https://www.meetup.com/optimizely-melbourne/events/288300244/&lt;/a&gt;&lt;/p&gt;&lt;h2 id=&quot;heading-meetup-details&quot;&gt;Meetup details&lt;/h2&gt;&lt;p&gt;Luminary and Optimizely are warmly welcoming back the Optimizely community with a mega meetup, including three great in-person talks (remote enabled). Hear about the latest platform developments as well as seeing some truly incredible projects in action.&lt;/p&gt;&lt;p&gt;This is an event for the entire Optimizely developer and user community.There will be three 20-minute short talks, each with social breaks in between:&lt;/p&gt;&lt;p&gt;A showcase of Optimizely multi-site best-practices, featuring Fuso Trucks from Ynze Nunnink, Luminary Optimizely Lead &amp;amp; Optimizely MVPThe evolution of personalisation  Nicola Ayan, Director of Technology and Growth, Optimizely APJUnveiling of exciting new developments from Optimizely (plus two books to give away!)For those attending the event in person, Luminary is catering with substantial food and premium drink options.&lt;/p&gt;&lt;p&gt;The three best questions from guests (as voted by our speakers) will each receive a $100 Prezzee Gift Card, redeemable at more than 250 major retailers Australia-wide.&lt;/p&gt;&lt;p&gt;We are so excited to share this event with you!&lt;/p&gt;&lt;h2 id=&quot;heading-how-to-join-online&quot;&gt;How to join online?&lt;/h2&gt;&lt;p&gt;Keep an eye on the &lt;a target=&quot;_blank&quot; href=&quot;https://www.meetup.com/optimizely-melbourne/events/288300244/&quot;&gt;meetup page&lt;/a&gt; for the latest details on how to join. It will be a zoom meeting and you will be able to communicate and ask questions!&lt;/p&gt;</description>            <guid>https://ynze.hashnode.dev/optimizely-community-meetup-sept-29</guid>            <pubDate>Tue, 27 Sep 2022 04:48:08 GMT</pubDate>           <category>Blog post</category></item><item> <title>How to bypass the content creation view in Optimizely</title>            <link>https://blog.ynzen.com/how-to-bypass-the-content-creation-view-in-optimizely</link>            <description>&lt;p&gt;Something that has come up a couple of times in the last few year is feedback from content editors about the editing view that comes up when creating new content. The view is based on properties (or content fields) that are required, so you cannot create the content before you&#39;ve provided values for those properties. The view only comes up if there are any required properties. &lt;/p&gt;
&lt;p&gt;It can be a real nuisance because it&#39;s at least one extra step to complete every time you need to create a piece of content. When working with lots of pages it can be a real time waster. In some cases the editor just wants to set up the IA without having to worry about all the required content on each page. In practice it&#39;s not that important to provide the required values when creating the content, but it is important that the required values are provided before the content can be published.&lt;/p&gt;
&lt;h2 id=&quot;heading-creating-a-metadata-extender-class&quot;&gt;Creating a metadata extender class&lt;/h2&gt;
&lt;p&gt;The cool thing is that there is actually a way to skip the initial &#39;create content&#39; editing view. The trick is to dynamically adjust the metadata of the properties to make them non-required when the content is being newly created. We need to make sure that the properties are still required &lt;em&gt;after&lt;/em&gt; the content has been created, so that it cannot be published before the values are provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SkipContentCreateViewMetadataExtender&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IMetadataExtender&lt;/span&gt;
{
    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ModifyMetadata&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ExtendedMetadata metadata, IEnumerable&amp;lt;Attribute&amp;gt; attributes&lt;/span&gt;)&lt;/span&gt;
    {
        &lt;span class=&quot;hljs-comment&quot;&gt;// When content is being created the content link is 0&lt;/span&gt;
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (metadata.Model &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; IContent data &amp;amp;&amp;amp; data.ContentLink.ID == &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)
        {
            &lt;span class=&quot;hljs-keyword&quot;&gt;foreach&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; modelMetadata &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; metadata.Properties)
            {
                &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; property = (ExtendedMetadata)modelMetadata;

                property.ShowForEdit = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;

                &lt;span class=&quot;hljs-comment&quot;&gt;//If property if required you won&#39;t be able to create the content&lt;/span&gt;
                &lt;span class=&quot;hljs-comment&quot;&gt;//Therefore set required to false&lt;/span&gt;
                &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (property.IsRequired)
                    property.IsRequired = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;heading-registering-the-metadata-extender&quot;&gt;Registering the metadata extender&lt;/h3&gt;
&lt;p&gt;Then in order for the metadata extender to work it needs to be registered using an initialization module. You can specify the content type that the metadata extender should be used for, in case you only want this to be used for a particular content type, and it also works inherently with base classes. In this case I used the &lt;code&gt;ContentData&lt;/code&gt; type, which means that the extender will be executed for practically any content. If for example you only want to use this for pages then you can change it to &lt;code&gt;PageData&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;ModuleDependency(typeof(InitializationModule))&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MetadataHandlerInitialization&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IInitializableModule&lt;/span&gt;
{
    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Initialize&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;InitializationEngine context&lt;/span&gt;)&lt;/span&gt;
    {
        &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; registry = context.Locate.Advanced.GetInstance&amp;lt;MetadataHandlerRegistry&amp;gt;();
        registry.RegisterMetadataHandler(&lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt;(ContentData), &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SkipContentCreateViewMetadataExtender());
    }

    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Uninitialize&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;InitializationEngine context&lt;/span&gt;)&lt;/span&gt;{ }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;heading-the-outcome&quot;&gt;The outcome&lt;/h2&gt;
&lt;p&gt;Now when you create any new content it will only the title will be required, making it much quicker to create a large amount of pages and blocks. On top of that required properties are still required before the content can be published.
&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1663738538761/KeYOMxnOb.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;heading-closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Quick shoutout to &lt;a target=&quot;_blank&quot; href=&quot;https://www.david-tec.com/2017/07/hiding-required-properties-on-the-create-new-page-in-episerver/&quot;&gt;this blog post by David Knipe&lt;/a&gt;, which inspired me to create the solution described in this article. Go check it out!&lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/how-to-bypass-the-content-creation-view-in-optimizely</guid>            <pubDate>Fri, 23 Sep 2022 04:43:51 GMT</pubDate>           <category>Blog post</category></item><item> <title>How to bypass the content creation view in Optimizely</title>            <link>https://ynze.hashnode.dev/how-to-bypass-the-content-creation-view-in-optimizely</link>            <description>&lt;p&gt;Something that has come up a couple of times in the last few year is feedback from content editors about the editing view that comes up when creating new content. The view is based on properties (or content fields) that are required, so you cannot create the content before you&#39;ve provided values for those properties. The view only comes up if there are any required properties. &lt;/p&gt;&lt;p&gt;It can be a real nuisance because it&#39;s at least one extra step to complete every time you need to create a piece of content. When working with lots of pages it can be a real time waster. In some cases the editor just wants to set up the IA without having to worry about all the required content on each page. In practice it&#39;s not that important to provide the required values when creating the content, but it is important that the required values are provided before the content can be published.&lt;/p&gt;&lt;h2 id=&quot;heading-creating-a-metadata-extender-class&quot;&gt;Creating a metadata extender class&lt;/h2&gt;&lt;p&gt;The cool thing is that there is actually a way to skip the initial &#39;create content&#39; editing view. The trick is to dynamically adjust the metadata of the properties to make them non-required when the content is being newly created. We need to make sure that the properties are still required &lt;em&gt;after&lt;/em&gt; the content has been created, so that it cannot be published before the values are provided.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;SkipContentCreateViewMetadataExtender&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IMetadataExtender&lt;/span&gt;{    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ModifyMetadata&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;ExtendedMetadata metadata, IEnumerable&amp;lt;Attribute&amp;gt; attributes&lt;/span&gt;)&lt;/span&gt;    {        &lt;span class=&quot;hljs-comment&quot;&gt;// When content is being created the content link is 0&lt;/span&gt;        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (metadata.Model &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; IContent data &amp;amp;&amp;amp; data.ContentLink.ID == &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;)        {            &lt;span class=&quot;hljs-keyword&quot;&gt;foreach&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; modelMetadata &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; metadata.Properties)            {                &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; property = (ExtendedMetadata)modelMetadata;                property.ShowForEdit = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;                &lt;span class=&quot;hljs-comment&quot;&gt;//If property if required you won&#39;t be able to create the content&lt;/span&gt;                &lt;span class=&quot;hljs-comment&quot;&gt;//Therefore set required to false&lt;/span&gt;                &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; (property.IsRequired)                    property.IsRequired = &lt;span class=&quot;hljs-literal&quot;&gt;false&lt;/span&gt;;            }        }    }}&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;heading-registering-the-metadata-extender&quot;&gt;Registering the metadata extender&lt;/h3&gt;&lt;p&gt;Then in order for the metadata extender to work it needs to be registered using an initialization module. You can specify the content type that the metadata extender should be used for, in case you only want this to be used for a particular content type, and it also works inherently with base classes. In this case I used the &lt;code&gt;ContentData&lt;/code&gt; type, which means that the extender will be executed for practically any content. If for example you only want to use this for pages then you can change it to &lt;code&gt;PageData&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;ModuleDependency(typeof(InitializationModule))&lt;/span&gt;]&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;MetadataHandlerInitialization&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;IInitializableModule&lt;/span&gt;{    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Initialize&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;InitializationEngine context&lt;/span&gt;)&lt;/span&gt;    {        &lt;span class=&quot;hljs-keyword&quot;&gt;var&lt;/span&gt; registry = context.Locate.Advanced.GetInstance&amp;lt;MetadataHandlerRegistry&amp;gt;();        registry.RegisterMetadataHandler(&lt;span class=&quot;hljs-keyword&quot;&gt;typeof&lt;/span&gt;(ContentData), &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; SkipContentCreateViewMetadataExtender());    }    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Uninitialize&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;InitializationEngine context&lt;/span&gt;)&lt;/span&gt;{ }}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;heading-the-outcome&quot;&gt;The outcome&lt;/h2&gt;&lt;p&gt;Now when you create any new content it will only the title will be required, making it much quicker to create a large amount of pages and blocks. On top of that required properties are still required before the content can be published.&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1663738538761/KeYOMxnOb.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;&lt;h2 id=&quot;heading-closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;&lt;p&gt;Quick shoutout to &lt;a target=&quot;_blank&quot; href=&quot;https://www.david-tec.com/2017/07/hiding-required-properties-on-the-create-new-page-in-episerver/&quot;&gt;this blog post by David Knipe&lt;/a&gt;, which inspired me to create the solution described in this article. Go check it out!&lt;/p&gt;</description>            <guid>https://ynze.hashnode.dev/how-to-bypass-the-content-creation-view-in-optimizely</guid>            <pubDate>Fri, 23 Sep 2022 04:43:51 GMT</pubDate>           <category>Blog post</category></item><item> <title>Conditional content properties in Optimizely</title>            <link>https://blog.ynzen.com/conditional-content-properties-in-optimizely</link>            <description>&lt;p&gt;In a recent Optimizely project we were building multiple sites that could share content types between them. One problem we encountered was that some of the blocks and pages required slightly different content, and therefore separate content fields (known as properties). The simplest solution is to have 2 properties and if one is empty then the other one is used. The page or block can then display the content accordingly depending on what the content editor has provided.&lt;/p&gt;
&lt;p&gt;The problem with that approach is that it becomes increasingly complex for the content editor and can result in content inconsistencies on the website. To solve this problem we developed a solution that could show or hide properties depending on the site that the shared content type is used. The use case in this article is for the properties to be conditional based on the site, however the same techniques can be applied to other similar use cases.&lt;/p&gt;
&lt;h2 id=&quot;heading-transform-metadata-with-custom-attributes&quot;&gt;Transform metadata with custom attributes&lt;/h2&gt;
&lt;p&gt;Metadata is very important and useful in the Optimizely CMS because it controls how content is rendered in the editing view. The metadata of a property contains a bunch of setting values for things such as if the property is required, disabled or even hidden entirely. There are multiple ways to influence the metadata of a property  one option is to create a custom attribute that implements the &lt;code&gt;IDisplayMetadataProvider&lt;/code&gt; interface. &lt;/p&gt;
&lt;h3 id=&quot;heading-hide-a-property-based-on-the-current-site&quot;&gt;Hide a property based on the current site&lt;/h3&gt;
&lt;p&gt;What we&#39;re trying to achieve here is to show the property only when it is rendered for a specific site. The code sample below contains a custom attribute that accepts a parameter containing names of the sites that the property should be available for. The &lt;code&gt;CreateDisplayMetadata&lt;/code&gt; method from the &lt;code&gt;IDisplayMetadataProvider&lt;/code&gt; interface is then used to dynamically update the metadata to only display the property if the current site matches those sites. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AvailableOnSitesAttribute&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;Attribute&lt;/span&gt;, &lt;span class=&quot;hljs-title&quot;&gt;IDisplayMetadataProvider&lt;/span&gt;
{
    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;&amp;gt; SiteNames{ &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }

    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AvailableOnSitesAttribute&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;[] siteNames&lt;/span&gt;)&lt;/span&gt;
    {
        SiteNames = siteNames;
    }

    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IsCurrentSite&lt;/span&gt;()&lt;/span&gt;
    {
        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; SiteNames.Any() &amp;amp;&amp;amp; SiteNames.Contains(SiteDefinition.Current.Name, StringComparer.InvariantCultureIgnoreCase);
    }

    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;CreateDisplayMetadata&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;DisplayMetadataProviderContext context&lt;/span&gt;)&lt;/span&gt;
    {
        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(context.DisplayMetadata.AdditionalValues[ExtendedMetadata.ExtendedMetadataDisplayKey] &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; ExtendedMetadata extendedMetadata)
        {
            extendedMetadata.ShowForEdit = IsCurrentSite();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are many use cases where this type of attribute can be useful. It doesn&#39;t necessarily have to be based on the current site, it can be completely conditional based on any requirements. One thing to note is that it requires a page reload for the metadata to update, meaning that it&#39;s not an ideal method when the condition is related to another property on the same content type.&lt;/p&gt;
&lt;p&gt;The custom attribute can be applied to any content type property. Below is a simple example of how a different introduction property can be displayed for the sites called Site1, Site2 and Site3. The names of the sites are configured in the &#39;manage sites&#39; section of the admin UI. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AvailableOnSites(&lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site1&quot;&lt;/span&gt;)&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; Intro { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }

[&lt;span class=&quot;hljs-meta&quot;&gt;AvailableOnSites(&lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site2&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site3&quot;&lt;/span&gt;)&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; XhtmlString DetailedIntro { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;heading-closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;The use case and examples in this article were somewhat simplified to make it less complex and confusing. To make things more robust you can adjust the metadata based on the site ID, or a site setting of sorts, instead of the name of the site.&lt;/p&gt;
&lt;p&gt;As a final note I suggest customising the functionality to meet your requirements and firstly considering all your options  before you end up overcomplicating the properties on your content types. Handling properties this way can be very powerful and this article is meant to help developers meet specific requirements. &lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/conditional-content-properties-in-optimizely</guid>            <pubDate>Wed, 21 Sep 2022 01:43:47 GMT</pubDate>           <category>Blog post</category></item><item> <title>Conditional content properties in Optimizely</title>            <link>https://ynze.hashnode.dev/conditional-content-properties-in-optimizely</link>            <description>&lt;p&gt;In a recent Optimizely project we were building multiple sites that could share content types between them. One problem we encountered was that some of the blocks and pages required slightly different content, and therefore separate content fields (known as properties). The simplest solution is to have 2 properties and if one is empty then the other one is used. The page or block can then display the content accordingly depending on what the content editor has provided.&lt;/p&gt;&lt;p&gt;The problem with that approach is that it becomes increasingly complex for the content editor and can result in content inconsistencies on the website. To solve this problem we developed a solution that could show or hide properties depending on the site that the shared content type is used. The use case in this article is for the properties to be conditional based on the site, however the same techniques can be applied to other similar use cases.&lt;/p&gt;&lt;h2 id=&quot;heading-transform-metadata-with-custom-attributes&quot;&gt;Transform metadata with custom attributes&lt;/h2&gt;&lt;p&gt;Metadata is very important and useful in the Optimizely CMS because it controls how content is rendered in the editing view. The metadata of a property contains a bunch of setting values for things such as if the property is required, disabled or even hidden entirely. There are multiple ways to influence the metadata of a property  one option is to create a custom attribute that implements the &lt;code&gt;IDisplayMetadataProvider&lt;/code&gt; interface. &lt;/p&gt;&lt;h3 id=&quot;heading-hide-a-property-based-on-the-current-site&quot;&gt;Hide a property based on the current site&lt;/h3&gt;&lt;p&gt;What we&#39;re trying to achieve here is to show the property only when it is rendered for a specific site. The code sample below contains a custom attribute that accepts a parameter containing names of the sites that the property should be available for. The &lt;code&gt;CreateDisplayMetadata&lt;/code&gt; method from the &lt;code&gt;IDisplayMetadataProvider&lt;/code&gt; interface is then used to dynamically update the metadata to only display the property if the current site matches those sites. &lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)&lt;/span&gt;]&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AvailableOnSitesAttribute&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;Attribute&lt;/span&gt;, &lt;span class=&quot;hljs-title&quot;&gt;IDisplayMetadataProvider&lt;/span&gt;{    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;&amp;gt; SiteNames{ &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;AvailableOnSitesAttribute&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;[] siteNames&lt;/span&gt;)&lt;/span&gt;    {        SiteNames = siteNames;    }    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;IsCurrentSite&lt;/span&gt;()&lt;/span&gt;    {        &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; SiteNames.Any() &amp;amp;&amp;amp; SiteNames.Contains(SiteDefinition.Current.Name, StringComparer.InvariantCultureIgnoreCase);    }    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;CreateDisplayMetadata&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;DisplayMetadataProviderContext context&lt;/span&gt;)&lt;/span&gt;    {        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt;(context.DisplayMetadata.AdditionalValues[ExtendedMetadata.ExtendedMetadataDisplayKey] &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; ExtendedMetadata extendedMetadata)        {            extendedMetadata.ShowForEdit = IsCurrentSite();        }    }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are many use cases where this type of attribute can be useful. It doesn&#39;t necessarily have to be based on the current site, it can be completely conditional based on any requirements. One thing to note is that it requires a page reload for the metadata to update, meaning that it&#39;s not an ideal method when the condition is related to another property on the same content type.&lt;/p&gt;&lt;p&gt;The custom attribute can be applied to any content type property. Below is a simple example of how a different introduction property can be displayed for the sites called Site1, Site2 and Site3. The names of the sites are configured in the &#39;manage sites&#39; section of the admin UI. &lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AvailableOnSites(&lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site1&quot;&lt;/span&gt;)&lt;/span&gt;]&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; Intro { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }[&lt;span class=&quot;hljs-meta&quot;&gt;AvailableOnSites(&lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site2&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;Site3&quot;&lt;/span&gt;)&lt;/span&gt;]&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; XhtmlString DetailedIntro { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;heading-closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;&lt;p&gt;The use case and examples in this article were somewhat simplified to make it less complex and confusing. To make things more robust you can adjust the metadata based on the site ID, or a site setting of sorts, instead of the name of the site.&lt;/p&gt;&lt;p&gt;As a final note I suggest customising the functionality to meet your requirements and firstly considering all your options  before you end up overcomplicating the properties on your content types. Handling properties this way can be very powerful and this article is meant to help developers meet specific requirements. &lt;/p&gt;</description>            <guid>https://ynze.hashnode.dev/conditional-content-properties-in-optimizely</guid>            <pubDate>Wed, 21 Sep 2022 01:43:47 GMT</pubDate>           <category>Blog post</category></item><item> <title>Content type mismatch exception in Optimizely</title>            <link>https://blog.ynzen.com/content-type-mismatch-exception-in-optimizely</link>            <description>&lt;p&gt;The &lt;code&gt;TypeMismatchException&lt;/code&gt; exception came up recently after making some changes to namespaces and assemblies in an Optimizely project. This proved to be an interesting issue and I will explain my findings from investigating the problem  in the hopes that it will help another developer in the future. &lt;/p&gt;
&lt;h3 id=&quot;heading-the-exception&quot;&gt;The exception&lt;/h3&gt;
&lt;p&gt;As we can see in the exception below, there is a problem with a piece of content that does not inherit from the correct content type. The content is an image and it inherits from &lt;code&gt;ImageData&lt;/code&gt; instead of the expected&lt;code&gt;ImageFile&lt;/code&gt; type. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;An unhandled exception occurred while processing the request.
TypeMismatchException: Content with id &#39;1234&#39; is of type &#39;EPiServer.Core.ImageData_DynamicProxy&#39; which does not inherit required type &#39;MyProject.Models.Media.ImageFile&#39;
EPiServer.Core.Internal.DefaultContentLoader.ThrowTypeMismatchException(ContentReference link, Type actual, Type required)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;heading-whats-the-problem&quot;&gt;What&#39;s the problem?&lt;/h3&gt;
&lt;p&gt;The exception is caused by an invalid object cast. In my case I was casting image content to &lt;code&gt;ImageFile&lt;/code&gt;, but the content had somehow changed to be of type &lt;code&gt;ImageData&lt;/code&gt;. What is actually happening is that the custom content type is no longer recognized and the primary type is used as a fallback. The type does still exist in the solution, but it has been moved to a new assembly and namespace. The problem is that the content types in the new assembly are not picked up by the CMS.&lt;/p&gt;
&lt;h4 id=&quot;heading-content-type-synchronization&quot;&gt;Content type synchronization&lt;/h4&gt;
&lt;p&gt;On startup the assemblies of the application are automatically scanned for content types and then those types are synchronized to the database. Any of the referenced assemblies can also contain content types and will be synchronized in the same way. It&#39;s interesting to note that this only works for assemblies with a dependency to the &lt;code&gt;EPiServer.Framework&lt;/code&gt; package. Besides that it also does not seem to work for assemblies that are added to the project as a direct DLL reference.&lt;/p&gt;
&lt;h3 id=&quot;heading-how-to-fix-it&quot;&gt;How to fix it&lt;/h3&gt;
&lt;p&gt;The new assembly that contains the content type was added to the project as a direct DLL reference. This assembly was not being picked up by the &lt;code&gt;AssemblyScanner&lt;/code&gt; during startup and therefore the content type was not synchronized correctly  like I described in the section above. In order to solve this issue I had to reference the assembly in a different way and opted for a private nuget package. Another way is to add a reference to the project instead of to the DLL directly. &lt;/p&gt;
&lt;h3 id=&quot;heading-conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The issue turned out to be caused by something quite different than I originally expected. It was interesting to learn about how the content type synchronization works in Optimizely. I doubt many people will encounter the same issue, because it&#39;s really an unlucky edge case with assembly referencing specific to my architecture. Although if anyone finds themselves in a similar situation then hopefully this post will help them.&lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/content-type-mismatch-exception-in-optimizely</guid>            <pubDate>Fri, 19 Aug 2022 05:25:56 GMT</pubDate>           <category>Blog post</category></item><item> <title>Content type mismatch exception in Optimizely</title>            <link>https://ynze.hashnode.dev/content-type-mismatch-exception-in-optimizely</link>            <description>&lt;p&gt;The &lt;code&gt;TypeMismatchException&lt;/code&gt; exception came up recently after making some changes to namespaces and assemblies in an Optimizely project. This proved to be an interesting issue and I will explain my findings from investigating the problem  in the hopes that it will help another developer in the future. &lt;/p&gt;&lt;h3 id=&quot;heading-the-exception&quot;&gt;The exception&lt;/h3&gt;&lt;p&gt;As we can see in the exception below, there is a problem with a piece of content that does not inherit from the correct content type. The content is an image and it inherits from &lt;code&gt;ImageData&lt;/code&gt; instead of the expected&lt;code&gt;ImageFile&lt;/code&gt; type. &lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;An unhandled exception occurred while processing the request.TypeMismatchException: Content with id &#39;1234&#39; is of type &#39;EPiServer.Core.ImageData_DynamicProxy&#39; which does not inherit required type &#39;MyProject.Models.Media.ImageFile&#39;EPiServer.Core.Internal.DefaultContentLoader.ThrowTypeMismatchException(ContentReference link, Type actual, Type required)&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;heading-whats-the-problem&quot;&gt;What&#39;s the problem?&lt;/h3&gt;&lt;p&gt;The exception is caused by an invalid object cast. In my case I was casting image content to &lt;code&gt;ImageFile&lt;/code&gt;, but the content had somehow changed to be of type &lt;code&gt;ImageData&lt;/code&gt;. What is actually happening is that the custom content type is no longer recognized and the primary type is used as a fallback. The type does still exist in the solution, but it has been moved to a new assembly and namespace. The problem is that the content types in the new assembly are not picked up by the CMS.&lt;/p&gt;&lt;h4 id=&quot;heading-content-type-synchronization&quot;&gt;Content type synchronization&lt;/h4&gt;&lt;p&gt;On startup the assemblies of the application are automatically scanned for content types and then those types are synchronized to the database. Any of the referenced assemblies can also contain content types and will be synchronized in the same way. It&#39;s interesting to note that this only works for assemblies with a dependency to the &lt;code&gt;EPiServer.Framework&lt;/code&gt; package. Besides that it also does not seem to work for assemblies that are added to the project as a direct DLL reference.&lt;/p&gt;&lt;h3 id=&quot;heading-how-to-fix-it&quot;&gt;How to fix it&lt;/h3&gt;&lt;p&gt;The new assembly that contains the content type was added to the project as a direct DLL reference. This assembly was not being picked up by the &lt;code&gt;AssemblyScanner&lt;/code&gt; during startup and therefore the content type was not synchronized correctly  like I described in the section above. In order to solve this issue I had to reference the assembly in a different way and opted for a private nuget package. Another way is to add a reference to the project instead of to the DLL directly. &lt;/p&gt;&lt;h3 id=&quot;heading-conclusion&quot;&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The issue turned out to be caused by something quite different than I originally expected. It was interesting to learn about how the content type synchronization works in Optimizely. I doubt many people will encounter the same issue, because it&#39;s really an unlucky edge case with assembly referencing specific to my architecture. Although if anyone finds themselves in a similar situation then hopefully this post will help them.&lt;/p&gt;</description>            <guid>https://ynze.hashnode.dev/content-type-mismatch-exception-in-optimizely</guid>            <pubDate>Fri, 19 Aug 2022 05:25:56 GMT</pubDate>           <category>Blog post</category></item><item> <title>Contextualizing content type thumbnails in Optimizely CMS</title>            <link>https://blog.ynzen.com/contextualizing-content-type-thumbnails-in-optimizely-cms</link>            <description>&lt;p&gt;In the CMS when creating a block or a page a list of all the available content types will be displayed. Each content type can have its own custom thumbnail image to help identify what the content is used for. One common practice is to take a screenshot of the component as it was designed and use that as the thumbnail. To configure the thumbnail image you can add the &lt;code&gt;ImageUrl&lt;/code&gt; attribute to any content type. The attribute has a parameter that can be used to set the path to the image.&lt;/p&gt;
&lt;p&gt;Now I had a scenario where a block is used in multiple different sites  and each site has its own styling theme. If I were to use a screenshot of the block design then the styling will would be incorrect for all but one of the sites. This is because the thumbnail path can only be configured once per block type. To solve this I created a simple solution that enables a different thumbnail to be displayed for a block type based on the context. Note that for this example I&#39;m only using block types, but it can be used for any content types.&lt;/p&gt;
&lt;p&gt;The following snippet contains a custom attribute that inherits from the aforementioned &lt;code&gt;ImageUrlAttribute&lt;/code&gt; and overrides the &lt;code&gt;Path&lt;/code&gt; variable  in order to get the thumbnail path based on the name of the current site.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ContextualImageUrlAttribute&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;ImageUrlAttribute&lt;/span&gt;
{
    &lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;ContextualImageUrlAttribute&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; name&lt;/span&gt;) : &lt;span class=&quot;hljs-title&quot;&gt;base&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt;.Empty&lt;/span&gt;)&lt;/span&gt;
    {
        FileName = name;
    }

    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; FileName { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; }

    &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;string&lt;/span&gt; Path =&amp;gt; &lt;span class=&quot;hljs-string&quot;&gt;$&quot;~/&lt;span class=&quot;hljs-subst&quot;&gt;{SiteDefinition.Current.Name}&lt;/span&gt;/Thumbnails/&quot;&lt;/span&gt; + FileName + &lt;span class=&quot;hljs-string&quot;&gt;&quot;.jpg&quot;&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this to work the thumbnails have to be stored inside folders that are named after each site. For example the path to a thumbnail for one site could be &lt;code&gt;/MySite/Thumbnails/custom-block.jpg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The new attribute can then be added to any content type. Here you can see it being used on a block.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-C#&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;ContextualImageUrl(&lt;span class=&quot;hljs-meta-string&quot;&gt;&quot;hero-banner-block&quot;&lt;/span&gt;)&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;HeroBannerBlock&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot;&gt;BlockData&lt;/span&gt;
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a simple example of how you can use a custom attribute implementation to contextualize the thumbnail path. In other scenario&#39;s you may want to change the path based on a configurable setting or some other contextual property. Hopefully this example can help developers make the editing experience more user friendly in a similar  multi-site solution.&lt;/p&gt;
</description>            <guid>https://blog.ynzen.com/contextualizing-content-type-thumbnails-in-optimizely-cms</guid>            <pubDate>Tue, 12 Jul 2022 04:23:53 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>