<?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 MilosR</title> <link>https://world.optimizely.com/blogs/milosr/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Beginner&#39;s Guide for Optimizely Backend Developers</title>            <link>https://world.optimizely.com/blogs/milosr/dates/2025/11/beginners-guide-for-optimizely-paas-backend-developers/</link>            <description>&lt;p&gt;Developing with Optimizely (formerly Episerver) requires more than just technical know‑how. It&amp;rsquo;s about respecting the editor&amp;rsquo;s perspective, ensuring performance, and applying conventions that make content management sustainable. Below are some of the most common mistakes that beginner Optimisely developers make that I have collected from number of different projects I had a chance to work on in the last couple of years.&lt;/p&gt;
&lt;p&gt;&lt;!--StartFragment --&gt;&lt;/p&gt;
&lt;h2&gt;Respect Editorial Settings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Always account for&amp;nbsp;&lt;strong&gt;languages, publish status, access rights, and personalization settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t rely solely on&amp;nbsp;&lt;code&gt;ContentRepository&lt;/code&gt; to fetch content &amp;mdash; editors can apply rules in edit mode that must be respected.&lt;/li&gt;
&lt;li&gt;In multilingual implementations, be sure to return correct language, taking care of fallback settings that editor can set.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Search &amp;amp; Navigation&lt;/strong&gt; APIs to retrieve lists of pages, applying filters for deleted or unpublished content, and excluding restricted pages like 404s.&lt;/li&gt;
&lt;li&gt;Also be prepared to have a fallback strategy in case &lt;strong&gt;Search &amp;amp; Navigation&lt;/strong&gt; API is not accessible.&lt;/li&gt;
&lt;li&gt;Take care of all LinkItem properties when rendering link on the page like title and target.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Caching Strategies&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cache &lt;strong&gt;global settings&lt;/strong&gt; and common page elements to improve performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always tend to cache view models&lt;/strong&gt;, never Optimizely data models, to avoid unnecessary memory consumption.&lt;/li&gt;
&lt;li&gt;Take care of cache invalidation strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Modeling Content Types&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Think from the &lt;strong&gt;editor&amp;rsquo;s perspective&lt;/strong&gt; when designing page types and blocks.&lt;/li&gt;
&lt;li&gt;Make sure to provide smooth editing experience.&lt;/li&gt;
&lt;li&gt;Apply SOLID programming principles&lt;/li&gt;
&lt;li&gt;Take care on usage of base classes, interfaces and inheritance.&lt;/li&gt;
&lt;li&gt;When modeling content types and properties always ask following questions:
&lt;ul&gt;
&lt;li&gt;is it going to be reusable?&lt;/li&gt;
&lt;li&gt;is it going to contain one or more items?&lt;/li&gt;
&lt;li&gt;is it global or site related?&lt;/li&gt;
&lt;li&gt;is it required or optional?&lt;/li&gt;
&lt;li&gt;can it have a default value?&lt;/li&gt;
&lt;li&gt;is it localizable?&lt;/li&gt;
&lt;li&gt;is it searchable?&lt;/li&gt;
&lt;li&gt;what can be validation rules?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;ContentArea&lt;/strong&gt; when you need:
&lt;ul&gt;
&lt;li&gt;Personalization&lt;/li&gt;
&lt;li&gt;Grouping blocks&lt;/li&gt;
&lt;li&gt;Applying grid settings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;XHtmlString&lt;/strong&gt; when you need rich text editing capability, but be aware that &lt;strong&gt;XHtmlString&lt;/strong&gt; is also a container for all other content types, similar as &lt;strong&gt;ContentArea&amp;nbsp;&lt;/strong&gt;it supports personalization&lt;/li&gt;
&lt;li&gt;Use blocks when you need reusable content or when you need to apply personalization in ContentArea.&lt;/li&gt;
&lt;li&gt;Use block as property type to group more properties, that also makes those properties easy editable in page preview mode&lt;/li&gt;
&lt;li&gt;Avoid nesting more than &lt;strong&gt;two levels of blocks&lt;/strong&gt; to prevent performance and indexing issues.&lt;/li&gt;
&lt;li&gt;For dynamic content nesting, prefer &lt;code&gt;IList&amp;lt;BlockType&amp;gt;&lt;/code&gt; property with BlockType defined as AvailableInEditMode = false.&lt;/li&gt;
&lt;li&gt;Learn and leverage &lt;strong&gt;out‑of‑the‑box property selections&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remember&lt;/strong&gt;: any change to a content type model mutates the database, except attribute values like &lt;code&gt;DisplayName&lt;/code&gt; or validation rules.&lt;/li&gt;
&lt;li&gt;Property removed from the code is NOT removed from the database, be sure to clean up those properties from Admin mode.&lt;/li&gt;
&lt;li&gt;When you need to validate input from editor, use validation attribute instead of Saving event.&lt;/li&gt;
&lt;li&gt;Determine carefully which content types can be nested within others, and specify which blocks are permitted in the Content Area.&lt;/li&gt;
&lt;li&gt;Avoid creating circular references between content types, as this can lead to infinite loops and cause problems with Find indexing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Scheduled Jobs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Scheduled jobs behave differently when run manually versus automatically.&lt;/li&gt;
&lt;li&gt;When manually triggered, that means you are logged in and it will use your user principal.&lt;/li&gt;
&lt;li&gt;Ensure the correct &lt;strong&gt;user principal&lt;/strong&gt; is set when running scheduled job automatically and when accessing protected content.&lt;/li&gt;
&lt;li&gt;Monitor for &lt;strong&gt;job stopped signals&lt;/strong&gt; during execution to handle interruptions gracefully.&lt;/li&gt;
&lt;li&gt;Be sure to &lt;strong&gt;disable&lt;/strong&gt; Scheduled Jobs in the development environment to avoid jobs running in the background without notice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Site and Domain Awareness&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;current domain&lt;/strong&gt; in the URL determines the active site.&lt;/li&gt;
&lt;li&gt;Review site settings carefully: a site with &lt;code&gt;*&lt;/code&gt; in its host list will respond to all domains pointing to that server.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Rich Text Editing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Configure &lt;strong&gt;TinyMCE&lt;/strong&gt; options: fonts, styles, plugins, allowed blocks, and personalization features.&lt;/li&gt;
&lt;li&gt;Understand the relationship between &lt;strong&gt;XHtmlString&lt;/strong&gt; and &lt;strong&gt;ContentArea&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;XHtmlString&lt;/code&gt; is a flexible container for any content, renders as a TinyMCE editor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ContentArea&lt;/code&gt; inherits from &lt;code&gt;XHtmlString&lt;/code&gt; and renders just as a content container.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Property Management&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Properties can define type, default values, and attributes such as &lt;code&gt;UIHint&lt;/code&gt;, &lt;code&gt;Validation&lt;/code&gt;, &lt;code&gt;Localization&lt;/code&gt;, &lt;code&gt;Display&lt;/code&gt;, and rendering options.&lt;/li&gt;
&lt;li&gt;Control how properties are rendered and their visibility in edit mode.&lt;/li&gt;
&lt;li&gt;Use events carefully, casting to the &lt;strong&gt;lowest possible type (e.g., interface)&lt;/strong&gt; to ensure correctness.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Find Indexing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Take care on content that needs to be indexed, add necessary indexing conventions.&lt;/li&gt;
&lt;li&gt;Take care when setting up development environment not to use Optimizely DXP indexing service URL, otherwise content from you local DB might end up there.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Optimizely development is not just about writing code &amp;mdash; it&amp;rsquo;s about creating a seamless experience for editors, ensuring performance, and applying conventions that scale. By respecting editorial settings, caching wisely, modeling content thoughtfully, and configuring tools like TinyMCE, developers can deliver solutions that empower both content creators and end users.&lt;/p&gt;
&lt;p&gt;&lt;!--EndFragment --&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/milosr/dates/2025/11/beginners-guide-for-optimizely-paas-backend-developers/</guid>            <pubDate>Wed, 10 Dec 2025 09:36:47 GMT</pubDate>           <category>Blog post</category></item><item> <title>Upgrade Optimizely CMS From v11 to v12</title>            <link>https://world.optimizely.com/blogs/milosr/dates/2024/5/upgrade-optimizely-cms-from-v11-to-v12/</link>            <description>&lt;h2&gt;Why Upgrade?&lt;/h2&gt;
&lt;p&gt;There are many improvements implemented in version 12, but most importantly is that the whole framework is migrated from .Net Framework 4 to .NET 6.&lt;/p&gt;
&lt;p&gt;Upgrade also contains significant security, performance and headless improvements, while Optimizely framework APIs remains the same so existing code won&#39;t break and there is no DB migration involved, except for user tables which need to migrate from AspNet Identity scheme to OpenId.&lt;/p&gt;
&lt;p&gt;Since it is now .Net Core native that also means it is OS agnostic and can be hosted on platforms other than Windows, like Linux for example.&lt;/p&gt;
&lt;h2&gt;How To Upgrade?&lt;/h2&gt;
&lt;p&gt;Suggested way is to use Optimizely Upgrade Assistant that will backup current solution and upgrade project to desired .NET Core version. The assistant will also try to migrate backend code and views, but that should be taken with caution. Developer needs to manually migrate from web.config to appsettings.json settings style, migration from global.asax to startup.cs, migration from OWIN and ASP.NET authentication to OpenID authentication, migration of block controllers to components, dependency injection, logging, JSON serialization etc.&lt;/p&gt;
&lt;p&gt;WebForms are no longer supported, which means MVC is the preferred way to render content.&lt;/p&gt;
&lt;p&gt;Upgrading from .Net Framework 4 to .NET 6 means that some of the NuGet packages used are no longer compatible and need either updating them to their .Net Core versions (and hoping they have compatible APIs) or replacing them with other packages or even rewriting some of the functionalities. That can&#39;t be predicted as every solution is specific and must be carefully analyzed before giving any estimations.&lt;/p&gt;
&lt;p&gt;Most importantly, take backups of both solution and DB before taking any actions. Prepare a new environment for testing of upgraded solution where all functionalities can be checked before replacing the old solution.&lt;/p&gt;
&lt;h2&gt;Step By Step Guide&lt;/h2&gt;
&lt;p&gt;I have tested Optimizely Upgrade Assistant on a simple project with few page types and blocks.&lt;/p&gt;
&lt;p&gt;First step is to install Assistant using .NET CLI tool by running command from Command Prompt:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;span&gt;dotnet tool install -g upgrade-assistant&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will install Microsoft Upgrade assistant from .Net Framework to .NET Core.&lt;/p&gt;
&lt;p&gt;Second step is to download Optimizely extension from Upgrade Assistant GitHub: &lt;a href=&quot;https://github.com/episerver/upgrade-assistant-extensions/releases&quot;&gt;https://github.com/episerver/upgrade-assistant-extensions/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Unpack files into temporary folder like: C:\Temp\EPi.Source.Updater.1.0.33&lt;/p&gt;
&lt;p&gt;Then, run the command to upgrade startup project:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;span&gt;upgrade-assistant upgrade {projectName}.csproj --extension C:\Temp\EPi.Source.Updater.1.0.33 --ignore-unsupported-features&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After completing upgrade procedure you&amp;rsquo;ll end up with lot of dependency issues (both old Episerver packages and new ones mixed), unresolved code issues, project not structured up to .Net Core standards (no wwwroot folder, static files are still in the project root), Program.cs and Startup.cs need adjustments, code from Global.asax.cs not migrated&amp;hellip;&lt;/p&gt;
&lt;p&gt;So, there is a lot of work to do for such a small project. Alternatively, instead of running upgrade assistant, I would start by creating a new solution with empty Optimizely CMS project based on .NET 6 (using Optimizely Visual Studio project template). Then, gradually transfer code from the old solution to the new one. Copy static files to wwwroot folder, copy backend files. Install missing NuGet packages but with their .Net Core versions. Some packages will have different APIs so you&amp;rsquo;ll need to update method signatures, or even rewrite some portions of code completely if the method is no longer supported.&lt;/p&gt;
&lt;p&gt;Keep in mind that .Net Core have a different dependency injection mechanism, logging, exception filters, JSON handling, block controllers are now components etc.&lt;/p&gt;
&lt;p&gt;There is no magic wand for this process and must be done manually. At the end, after the project is successfully building, migrate DB users using script located in EPi.Source.Updater.1.0.33 folder.&lt;/p&gt;
&lt;h2&gt;Should I Upgrade?&lt;/h2&gt;
&lt;p&gt;That decision needs to be taken by answering some questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;do I plan further development of current solution? If yes, then you should consider upgrading&lt;/li&gt;
&lt;li&gt;am I satisfied with the performance of the current solution? If not, then you should consider upgrading&lt;/li&gt;
&lt;li&gt;is investment in upgrading going to bring considerable value to the company? If yes, then you should consider upgrading&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/migration/proper-to-2x/?view=aspnetcore-6.0&quot;&gt;https://learn.microsoft.com/en-us/aspnet/core/migration/proper-to-2x/?view=aspnetcore-6.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/episerver/upgrade-assistant-extensions&quot;&gt;https://github.com/episerver/upgrade-assistant-extensions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.developers.optimizely.com/content-management-system/docs/upgrading-to-content-cloud-cms-12&quot;&gt;https://docs.developers.optimizely.com/content-management-system/docs/upgrading-to-content-cloud-cms-12&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.developers.optimizely.com/content-management-system/docs/upgrade-assistant&quot;&gt;https://docs.developers.optimizely.com/content-management-system/docs/upgrade-assistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/bca3f9b849324bbebf6f3df06d9bf013.aspx?vid=300437&quot;&gt;https://world.optimizely.com/resources/videos/video/?vid=300437&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Fvys7G2Vdvc&quot;&gt;https://www.youtube.com/watch?v=Fvys7G2Vdvc&lt;/a&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/milosr/dates/2024/5/upgrade-optimizely-cms-from-v11-to-v12/</guid>            <pubDate>Mon, 13 May 2024 09:04:37 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely Unit Testing Using CmsContentScaffolding Package</title>            <link>https://world.optimizely.com/blogs/milosr/dates/2024/4/optimizely-unit-testing-using-cmscontentscaffolding-package/</link>            <description>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Unit tests shouldn&#39;t be created just for business logic, but also for the content and rules defined for content creation (available content types, validations etc.). That way Optimizely solution becomes resilient to content type and rule changes.&lt;/p&gt;
&lt;p&gt;Usually there is a local development DB or test DB that is used continuously and, as test content is created in Optimizely and backend development is continuously changing content types and validation rules, it rarely gets verified as if content is created from scratch. That can lead to false thinking that test content represents realistic picture of what client will be able to create, but those issues will start popping up on staging environment as soon as client starts with content creation.&lt;/p&gt;
&lt;p&gt;The benefit you get with content unit tests is verification of desired site structure and validation rules as soon as change is introduced and always starts with empty DB from scratch, verification that controller output model contains all data needed. In order to easily outline content structure there is a library called CmsContentScaffolding that uses simple fluent syntax. It can create complete site structure, easily create large number of pages and prepare content for testing.&lt;/p&gt;
&lt;h1&gt;CmsContentScaffolding Package&lt;/h1&gt;
&lt;p&gt;Added as a Nuget package and registered service dependency using: &lt;em&gt;&lt;strong&gt;services.AddCmsContentScaffolding();&lt;/strong&gt;&lt;/em&gt;, followed with &lt;span&gt;IApplicationBuilder&lt;/span&gt; extension method &lt;em&gt;&lt;strong&gt;appBuilder.UseCmsContentScaffolding(builderOptions: o =&amp;gt; {}, builder: b =&amp;gt; {});&lt;/strong&gt;&lt;/em&gt;. There are two sections: &lt;strong&gt;builderOptions&lt;/strong&gt; (provides global options like language used, site host, site name, publish or just create page drafts, users and roles...) and &lt;strong&gt;builder &lt;/strong&gt;that exposes methods for content creation. You can create multiple sites by using another section of &lt;em&gt;&lt;strong&gt;appBuilder.UseCmsContentScaffolding(builderOptions: o =&amp;gt; {}, builder: b =&amp;gt; {});&lt;/strong&gt;&lt;/em&gt;. Builder have two main methods UseAssets(ContentReference.SiteBlockFolder) and UsePages(ContentReference.RootPage) which serves as a starting points from where content creation starts.&lt;/p&gt;
&lt;p&gt;Additionally, there is a package with helper methods for fake texts, images and videos, called CmsContentScaffolding.Shared.Resources.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;UseCmsContentScaffolding&lt;/span&gt; method has many options and can look like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.UseCmsContentScaffolding(&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; builderOptions: o =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; o.Language = CultureInfo.GetCultureInfo(&quot;sr&quot;);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; o.StartPageType = typeof(StartPage);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; o.BuildMode = BuildMode.Append;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; },&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; builder: b =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var teaser2Ref = ContentReference.EmptyReference;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var teaser3Ref = ContentReference.EmptyReference;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var articlePageRef = ContentReference.EmptyReference;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.UseAssets(ContentReference.SiteBlockFolder)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithFolder(&quot;Folder 1&quot;, l1 =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; l1&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithFolder(&quot;Folder 1_1&quot;, l2 =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; l2.WithBlock&amp;lt;TeaserBlock&amp;gt;(&quot;Teaser 1&quot;, out teaser2Ref, x =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Heading = &quot;Teaser 1 Heading&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Text = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Image = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 2&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithMedia&amp;lt;VideoFile&amp;gt;(x =&amp;gt; x.Name = &quot;Test video&quot;, ResourceHelpers.GetVideoStream(), &quot;.mp4&quot;)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithBlock&amp;lt;TeaserBlock&amp;gt;(&quot;Teaser 3&quot;, out teaser3Ref, x =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Heading = &quot;Test&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Text = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; x.Image = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 2&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithContent&amp;lt;ContentFolder&amp;gt;(x =&amp;gt; x.Name = &quot;Folder1&quot;)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithContent&amp;lt;ImageFile&amp;gt;(x =&amp;gt; x.Name = &quot;Image 1&quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;b.UsePages(ContentReference.RootPage)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithStartPage&amp;lt;StartPage&amp;gt;(p =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.Name = &quot;Home Page&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainContentArea&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddExistingItems(teaser2Ref, teaser3Ref)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddItem&amp;lt;TeaserBlock&amp;gt;(&quot;Start Page Teaser&quot;, b =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Heading = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Text = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Image = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 2&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, CultureInfo.GetCultureInfo(&quot;sv&quot;), t =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; t.Name = &quot;Start Page [SV]&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, l1 =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; l1&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithPage&amp;lt;ArticlePage&amp;gt;(out articlePageRef, p =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.Name = &quot;article1&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MetaTitle = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.TeaserText = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainBody&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddStringFragment(ResourceHelpers.Faker.Lorem.Paragraphs())&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddContentFragment(PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 1&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream()))&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddStringFragment(ResourceHelpers.Faker.Lorem.Paragraphs());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.PageImage = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 1&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainContentArea&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddItem&amp;lt;JumbotronBlock&amp;gt;(&quot;Jumbotron Block&quot;, b =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.ButtonText = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.ButtonLink = new Url(ResourceHelpers.Faker.Internet.Url());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Heading = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddItem&amp;lt;ImageFile&amp;gt;(options: i =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; i.Name = &quot;Test Image&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; i.ContentLink = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 1&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddExistingItem(teaser3Ref);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .WithPages&amp;lt;ArticlePage&amp;gt;(p =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.Name = &quot;Article 33&quot;;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MetaTitle = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.TeaserText = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainBody.AddStringFragment(ResourceHelpers.Faker.Lorem.Paragraphs(10));&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainContentArea.AddExistingItem(teaser2Ref);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, 100);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; })&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;.WithPages&amp;lt;ArticlePage&amp;gt;(p =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.Name = ResourceHelpers.Faker.Lorem.Slug(2);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; p.MainContentArea&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .AddItems&amp;lt;TeaserBlock&amp;gt;(b =&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Heading = ResourceHelpers.Faker.Lorem.Slug();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Text = ResourceHelpers.Faker.Lorem.Paragraph();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; b.Image = PropertyHelpers.GetOrAddMedia&amp;lt;ImageFile&amp;gt;(&quot;Image 1&quot;, &quot;.png&quot;, ResourceHelpers.GetImageStream());&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, 10);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, 10)&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; });&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It allows you to create assets structure, reuse created content by fetching it&#39;s references, create translated versions, create sub items without limit, create many content items of the same type using &lt;em&gt;&lt;strong&gt;total &lt;/strong&gt;&lt;/em&gt;parameter which can be any number between 1 and 10000, multiple sites with specific users and roles assigned etc.&lt;/p&gt;
&lt;p&gt;Package is open source and can be found on this URL: &lt;a href=&quot;https://github.com/milosranko/CmsContentScaffolding&quot;&gt;&lt;span&gt;https://github.com/milosranko/CmsContentScaffolding&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Unit Test Fundamentals&lt;/h1&gt;
&lt;p&gt;We can identify five important points when it comes to creating unit tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Defining a unit test&lt;/li&gt;
&lt;li&gt;Should be automated and repeatable&lt;/li&gt;
&lt;li&gt;Should be easy to implement and read using AAA pattern (Arrange, Act, Assert)&lt;/li&gt;
&lt;li&gt;Should be fully isolated&lt;/li&gt;
&lt;li&gt;Cleanup resources after tests are finished&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We will first initialize test using dedicated unit test DB (special SQL Server user should be created with sa rights to create and delete DB), then we will seed content using CmsContentScaffolding, write tests and clean up resources (including DB) after tests are finished.&lt;/p&gt;
&lt;p&gt;Unit test can be performed using contentLoader to get desired content and verify it is created invoking controller action.&lt;/p&gt;
&lt;h1&gt;Example&lt;/h1&gt;
&lt;p&gt;In this example we will see how test method can look like when implemented using triple A principle and when controller method is invoked. We verify that the output view model is having expected state.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[TestMethod]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;public void StartPageController_ShouldReturnIndexViewModel()&lt;/code&gt;&lt;br /&gt;&lt;code&gt;{&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; //Arrange&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var contentLoader = ServiceLocator.Current.GetRequiredService&amp;lt;IContentLoader&amp;gt;();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var siteDefinitionRepository = ServiceLocator.Current.GetRequiredService&amp;lt;ISiteDefinitionRepository&amp;gt;();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var siteDefinition = siteDefinitionRepository&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .List()&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .Where(x =&amp;gt; x.GetHosts(CultureInfo.GetCultureInfo(&quot;sr&quot;), false).Any())&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .Single();&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var startPage = contentLoader.Get&amp;lt;StartPage&amp;gt;(siteDefinition.StartPage);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var _controller = new StartPageController();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; //Act&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; var res = (ViewResult)_controller.Index(startPage);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; //Assert&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; Assert.IsNotNull(res);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; Assert.IsInstanceOfType(res.Model, typeof(PageViewModel&amp;lt;StartPage&amp;gt;));&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&amp;nbsp; &amp;nbsp; Assert.IsNotNull(((PageViewModel&amp;lt;StartPage&amp;gt;)res.Model).CurrentPage.MainContentArea);&lt;/code&gt;&lt;br /&gt;&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Constant verification of implemented code is crucial for continuous software development. In Optimizely CMS framework content and site structure are crucial components and must be maintained and constantly verified in order to avoid unwanted issues when you less expect them. I have used CmsContentScaffolding library on last project and found it very helpful, either as a unit test helper or to quickly scaffold site structure and perform manual tests and investigate bugs.&lt;/p&gt;
&lt;p&gt;I would like to ask you to check it out and leave a comment on this blog post.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/milosranko/CmsContentScaffolding&quot;&gt;&lt;span&gt;https://github.com/milosranko/CmsContentScaffolding&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Appendix&lt;/h1&gt;
&lt;p&gt;CmsContentScaffolding package can be also used to outline products catalog in Optimizely Customized Commerce solution.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;notranslate&quot;&gt;&lt;code&gt;app.UseCmsContentScaffolding(
    builderOptions: o =&amp;gt;
    {
        o.SiteName = &quot;Demo&quot;;
        o.Language = CultureInfo.GetCultureInfo(&quot;en&quot;);
        o.SiteHost = &quot;https://localhost:5000&quot;;
        o.BuildMode = BuildMode.Append;
        o.StartPageType = typeof(StartPage);
    },
    builder: b =&amp;gt;
    {
        b.UseAssets(referenceConverter.GetRootLink())
        .WithContent&amp;lt;CatalogContent&amp;gt;(x =&amp;gt;
        {
            x.Name = &quot;Catalog 1&quot;;
            x.DefaultCurrency = &quot;EUR&quot;;
            x.DefaultLanguage = &quot;en&quot;;
            x.WeightBase = &quot;kgs&quot;;
            x.LengthBase = &quot;cm&quot;;
        }, l1 =&amp;gt; l1.WithContent&amp;lt;FashionNode&amp;gt;(x =&amp;gt; x.Name = &quot;Men&quot;, l2 =&amp;gt;
                    l2.WithContent&amp;lt;FashionNode&amp;gt;(x =&amp;gt; x.Name = &quot;Shoes&quot;, l3 =&amp;gt;
                        l3.WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 1&quot;, l4 =&amp;gt;
                            l4
                            .WithContent&amp;lt;FashionVariant&amp;gt;(v =&amp;gt; v.Name = &quot;Variant 1&quot;)
                            .WithContent&amp;lt;FashionVariant&amp;gt;(v =&amp;gt; v.Name = &quot;Variant 2&quot;))
                        .WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 2&quot;)
                        .WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 3&quot;)
                 ).WithContent&amp;lt;FashionNode&amp;gt;(x =&amp;gt; x.Name = &quot;Accessories&quot;, l3 =&amp;gt;
                    l3
                    .WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 1&quot;)
                    .WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 2&quot;)
                    .WithContent&amp;lt;FashionProduct&amp;gt;(x =&amp;gt; x.Name = &quot;Product 3&quot;)
                 )
            )
        );
    });&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/milosr/dates/2024/4/optimizely-unit-testing-using-cmscontentscaffolding-package/</guid>            <pubDate>Fri, 26 Apr 2024 09:49:47 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>