<rss version="2.0"><channel xml:base="https://www-preprod.davidhome.net/rss/"><title>Blog</title><link>https://www-preprod.davidhome.net/</link><description>Welcome to my personal blog!</description><lastBuildDate>Fri, 24 Apr 2026 22:00:00 Z</lastBuildDate><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/my-blog-is-now-running-using-optimizely-cms/</guid><link>https://www-preprod.davidhome.net/blog/my-blog-is-now-running-using-optimizely-cms/</link><category>Optimizely</category><title>My blog is now running using Optimizely CMS!</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;It's official! You are currently reading this post on my shiny new Optimizely CMS website.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In the past weeks, I have been quite busy crunching every items to transfer my originally developed website from Orchard CMS to Optimizely CMS. It was quite the experience and also, a lot of new opportunities are now on the horizon.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Now what?&lt;/h2&gt;
&lt;p&gt;During this experience, I was able to take my time to tackle certain architectural challenges that we're often facing when creating a new Optimizely CMS website. So in the upcoming days/weeks, I will be able to talk about these, but here's a sneak&amp;nbsp;peek on the topics I'd like to talk about:&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What's the right project structure, or at least, best&amp;nbsp;&lt;em&gt;suggested&lt;/em&gt;&amp;nbsp;structure,&amp;nbsp;when building the project with a multi site setup in mind.&lt;/li&gt;
&lt;li&gt;Creation &amp;amp; announcement of a new NuGet feed that will ease the multi site setup for developers.&lt;/li&gt;
&lt;li&gt;For those interested, I have also developed a custom RSS feed integration for Optimizely CMS that I plan on publishing. You can see it in action on this blog:&amp;nbsp;&lt;a href="https://www-preprod.davidhome.net/rss/"&gt;https://www.davidhome.net/rss/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;GitLab pipelines. I have fully automated builds &amp;amp; deployments of the website using this technology. I'm normally accustomed to use Azure DevOps, but for this personal project, it was a great opportunity to learn something different using my own GitLab instance.&lt;/li&gt;
&lt;li&gt;Maybe an open topic on the mediator pattern? I've used the library MediatR for my blog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned &amp;amp; happy coding!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Tue, 22 Jul 2025 18:35:14 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/optimizely-content-graph-sync-computed-getter-properties-like-with-search-navigation/</guid><link>https://www-preprod.davidhome.net/blog/optimizely-content-graph-sync-computed-getter-properties-like-with-search-navigation/</link><category>Optimizely</category><title>Optimizely Content Graph - Sync Computed Getter Properties like with Search &amp; Navigation</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;Recently, I have been re-writing my blog using Optimizely CMS. During this process, I wanted to try new released technologies offered by Optimizely. Today, I'm going to talk a bit about my experience with Content Graph.&lt;/p&gt;
&lt;p&gt;Of course, I thought about using Search &amp;amp; Navigation, but I think it was beneficial that I learned something that will eventually, to my opinion, replace it entirely.&lt;/p&gt;
&lt;p&gt;With Search &amp;amp; Navigation, the majority of Optimizely developers are well aware that you can synchronize not only properties meant for the content type itself, but also the ones that are computed, meaning only a getter has been used. The following example demonstrate that (see FirstPublishedDate):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/22ee54e089604fc4b4e70b23b51da9c7/screenshot202024-10-18201538161.png"&gt;&lt;img src="https://www-preprod.davidhome.net/contentassets/22ee54e089604fc4b4e70b23b51da9c7/screenshot202024-10-18201538161.png" alt="Figure 1" width="160" height="67" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you use Search &amp;amp; Navigation, this property is getting indexed and can be used on any query. It also means you can order your result with that computed field and do a bunch of cool tricks, such as including different ways to represent a certain element from the content type in a fashion consumable by the Search &amp;amp; Navigation C# client. Complex types aren&amp;rsquo;t really its force, so we often add getter only properties to return a more simplified, but useful, data format.&lt;/p&gt;
&lt;p&gt;So, under Content Graph, I was under the impression that I could do the same thing, but alas, no, you can't for the moment. But Optimizely did confirmed to myself via a support request they have plans to add something that would allow developers to customize and add the ability to extend the data synchronization of content types. In the meantime, I have concocted a very... but very hackish workaround that allow you to synchronize getter properties. Before moving forward with the solution, a little explanation is in order to understand a bit more what you can/can't do:&lt;/p&gt;
&lt;p&gt;The schedule job "Optimizely Graph content synchronization job" is the main responsible to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scan for all existing content types and synchronize them to Content Graph. It's using their definitions to know the structure your content should have under its own engine.&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;By the way, you can also use their endpoints to synchronize content types completely unrelated to the CMS:&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/platform-optimizely/v1.4.0-optimizely-graph/docs/synchronize-content-types"&gt;https://docs.developers.optimizely.com/platform-optimizely/v1.4.0-optimizely-graph/docs/synchronize-content-types&lt;/a&gt;. Very useful in scenarios where you need content from different external sources, but all available in the same place. Super powerful and allow the leverage of the engine to do extremely useful searches.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Then scans for all existing content in the CMS and synchronize all of its data under Content Graph.&amp;nbsp;&lt;strong&gt;This is the step that interests us&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While digging for a solution to extend my content type and allow the synchronization of getter only properties, I have realized the following must be respected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your property&amp;nbsp;&lt;strong&gt;cannot&lt;/strong&gt;&amp;nbsp;be a getter only, you still have to put a setter. It's because otherwise the property is not considered a&amp;nbsp;&lt;em&gt;real&lt;/em&gt;&amp;nbsp;CMS property and thus is getting ignored during the indexation. A&amp;nbsp;&lt;em&gt;real&lt;/em&gt;&amp;nbsp;property will appear under "CMS -&amp;gt; Settings -&amp;gt; Content Type" menu.&lt;/li&gt;
&lt;li&gt;So I simply added a setter using&amp;nbsp;&lt;code&gt;this.SetPropertyValue()&lt;/code&gt;&amp;nbsp;to get over this limitation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you get your property synchronized to Optimizely database comes the part where the indexation job of Content Graph is still ignoring it. While reading the decompiled code of Optimizely, I learned that the current business logic is&amp;nbsp;&lt;strong&gt;only&lt;/strong&gt;&amp;nbsp;relying on the data stored within the CMS database, which means that in the end&amp;nbsp;&lt;strong&gt;all&lt;/strong&gt;&amp;nbsp;defined elements in the code is entirely ignored, since the job is only directly pulling the data inside the internal content type tables.&lt;/p&gt;
&lt;p&gt;So, well, this is where I come with the idea to build a dynamic type. I had to create a dynamic assembly which was allowed to interact with "internal" types of the "Optimizely.ContentGraph.Cms.NetCore" assembly. In the end, I had to create IL code so that my desired behavior could be added to the synchronization job. To keep that brief, if you want the solution, here's my&amp;nbsp;&lt;a href="https://github.com/ddprince17/optimizely-content-graph"&gt;GitHub repository&lt;/a&gt;&amp;nbsp;with a small example. The dynamic type is inheriting from "ContentGraphContentConverter" and is replacing the original type under the IoC container. Under the "Convert" method, instead of directly returning the model, I'm calling the extension AddReadonlyProperties, which then allow me to customize the return value.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Fri, 18 Oct 2024 04:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/getnextsegment-with-empty-remaining-causing-fuzzes/</guid><link>https://www-preprod.davidhome.net/blog/getnextsegment-with-empty-remaining-causing-fuzzes/</link><category>Optimizely</category><title>GetNextSegment with empty Remaining causing fuzzes</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;Optimizely CMS offers you to create partial routers. This concept allows you display content differently depending on the routed content in the URL. Of course, a more in-dept explanation can be found at the following URL:&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/example-of-news-partial-routing"&gt;https://docs.developers.optimizely.com/content-management-system/docs/example-of-news-partial-routing&lt;/a&gt;. All in all, this concept also existed in the previous version of the CMS, but it differed a bit than it is currently.&lt;/p&gt;
&lt;p&gt;See, when we migrated a customer from .NET Framework to .NET Core, AKA CMS 11 to 12, we encountered a memory leak that we couldn't really put our hand on at first sight. The solution heavily used those partial routers. Our issue also often occurred on DXP and not on our development machine, which was even weirder. Until we remembered that the application is automatically&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites"&gt;warmed up&lt;/a&gt;&amp;nbsp;when running under DXP. This led us to find the source of the problem, the partial routers, but more specifically on the part when we get the next value of the URL segment.&lt;/p&gt;
&lt;p&gt;As explained in the&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/example-of-news-partial-routing"&gt;CMS 12 documentation&lt;/a&gt;, you can use from an&amp;nbsp;&lt;code&gt;UrlResolverContext&lt;/code&gt;&amp;nbsp;the method&amp;nbsp;&lt;code&gt;GetNextSegment&lt;/code&gt;&amp;nbsp;to get that information. It returns a&amp;nbsp;&lt;code&gt;Segment&lt;/code&gt;, which contains what's "next" and the "remaining path". Our code, since it was originally developed for CMS 11, used at the time the old API, a method "GetNextValue", probably from a similar service which I don't reminder the name and was looping around it until Optimizely code returned an empty segment. It all makes sense right? Here's the decompiled code from CMS 11:&amp;nbsp;&lt;img src="https://www-preprod.davidhome.net/contentassets/d4ddd2d2f5c440eca0ded2148cedf170/image0011.png" alt="Image 1" width="1278" height="760" /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, CMS 11 was simply returning an empty "next" and "remaining"&amp;nbsp;&lt;code&gt;SegmentPair&lt;/code&gt;&amp;nbsp;if the remaining path was empty. Our code was relying on this information, well, specifically the property "Remaining" of the segment pair. I'm sure at this point you're probably realizing why we were having memory leaks. Indeed, under CMS 12, this piece of code changed and caused infinite loops in ours, concatenating string&amp;nbsp;&lt;em&gt;to infinity and beyond&lt;/em&gt;. The change itself is very inexplicable I would say, even myself don't understand why Optimizely decided to make it that way, it feels more like an unwanted error than anything else. Here's a snapshot of the decompiled code, this will speak for itself:&amp;nbsp;&lt;img src="https://www-preprod.davidhome.net/contentassets/d4ddd2d2f5c440eca0ded2148cedf170/screenshot202024-07-07202327571.png" alt="Image 2" width="1252" height="956" /&gt;&lt;/p&gt;
&lt;p&gt;It, hum, well, reassign the remaining path if it's empty, but also have&amp;nbsp;&lt;strong&gt;the same check just after&lt;/strong&gt;, a condition, which at this point, will never be met, that simply does&amp;nbsp;&lt;strong&gt;what we would expect&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We informed Optimizely of this quirk, though unfortunately didn't ended up being fixed. Our code now has different checks and also a failsafe to avoid looping indefinitely. I hope that with this post can save you time diagnosing similar problems on your end.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Sun, 07 Jul 2024 04:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/how-ndepend-can-quickly-help-you-find-code-quality-issues-and-resolve-them/</guid><link>https://www-preprod.davidhome.net/blog/how-ndepend-can-quickly-help-you-find-code-quality-issues-and-resolve-them/</link><category>Optimizely</category><title>How NDepend can quickly help you find code quality issues and resolve them</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;NDepend has been around&amp;nbsp;&lt;a href="https://web.archive.org/web/20060518122151/https://www.ndepend.com/"&gt;for quite a long time&lt;/a&gt;. If you are unaware of what it does, you should be checking it. It's an analysis tools which helps you find inconsistencies and discrepancies in your C# source code. I've been trying it out recently and helped me found certain issue which I wasn't aware of.&lt;/p&gt;
&lt;p&gt;To be clear, there is indeed other available solution and it's up to your team to decide which fits best with you. In this case, NDepend runs independently from any IDE, but you can install an integration plugin inside Visual Studio. This can give the leverage/advantage to avoid loading any IDE to obtain a report. Unfortunately, there is no plugin/extension support under JetBrains Rider, my personal IDE of choice, like there is under Visual Studio. I hope their team can implement one, that would be greatly valuable.&lt;/p&gt;
&lt;h3&gt;Dashboard&lt;/h3&gt;
&lt;p&gt;Once a solution analysis is completed, you will be greeted with the dashboard tab:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-22201216421.png"&gt;&lt;img title="Figure 1" src="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-22201216421.png" alt="Figure 1" width="160" height="87" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here lies a general overview of the quality of the whole solution that you have analyzed. This gives a great quick shot of the elements you could get your hand onto if your goal is to refactor the code to make it more maintainable. You can personalize the rules and change them to your likings. For example, in my previous screenshot, there is one critical rule that flags I'm using a type within another type which have both different namespaces.&amp;nbsp;&lt;a href="https://www.ndepend.com/default-rules/NDepend-Rules-Explorer.html?ruleid=ND1400#!"&gt;The debt explanation is interesting&lt;/a&gt;. Of course, you can always decide to ignore it, but it gives an interesting point of view regarding the fact that some developers are using folders within a project to "organize" classes, which can lead to this kind of rule alert. Of course, everything is to take with a grain of salt, as I agree with someone from the comments of&amp;nbsp;&lt;a href="https://stackoverflow.com/questions/59519084/how-to-avoid-namespaces-dependency-cycles-between-my-entities"&gt;this Stack Overflow thread&lt;/a&gt;, these kind of alerts can be opinion based. An interesting thing that NDepend's team could be adding inside their analysis program is rule categorization; Opiniated ones versus the others by example. It could help to quickly address undiscussable items and leave any development team to discuss the other opiniated points and see whether they need to be addressed.&lt;/p&gt;
&lt;h3&gt;Dependency Graph&lt;/h3&gt;
&lt;p&gt;I think the most interesting feature of NDepend is the dependency graph. It's so incredibly useful to quickly identify areas of your code that aren't supposed to inherit another project within your solution. This can otherwise, to my opinion, often lead to solutions having classes/elements heavily coupled/entangled together. Based on the same project that I have used to create my first screenshot, here is an example of the graph:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23200946081.png"&gt;&lt;img title="Figure 2" src="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23200946081.png" alt="Figure 2" width="160" height="82" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can realize in the previous screenshot, there is a design pattern here that I often follow in projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The website is the main "referrer" of projects, meaning that nothing should be referencing the website.
&lt;ul&gt;
&lt;li&gt;The exception are the unit tests. You can also realize the only additional reference of the test projects are the "Models" projects. Nothing else.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The "Bootstrappers" projects serve only the purpose to add inside the IoC container the necessary services that are consumable by all "Contracts" projects.&lt;/li&gt;
&lt;li&gt;Each consumable "Contracts" projects comes in pair with a minimum of an additional "concrete" implementation project. These projects, as previously mentioned, only contain the implementation your contracts, which will then be consumed by the website.&lt;/li&gt;
&lt;li&gt;Almost everything consumes the "Models". This is again, by design. Models are the basically "the end of the road", it should generally refer nothing else.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;I will be doing another blog post about the design pattern I personally follow in projects.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;NDepend quickly identified a discrepancy in my project: There is a direct reference from the website to the database library and this is not normal. Double clicking on the arrow gives me the following:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201034241.png"&gt;&lt;img title="Figure 3" src="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201034241.png" alt="Figure 3" width="160" height="81" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With that, I can easily remove the three direct references from my code and refactor to use my repository pattern implementation. It's one of many example that can clearly help you and your team to identify elements such as this one.&lt;/p&gt;
&lt;h3&gt;Metrics&lt;/h3&gt;
&lt;p&gt;The "Metrics" tab is again another great example of the tool usefulness; From there you can view "heated" areas of your code which has strong cyclomatic complexity. Here in my example, the following method is considered a bit more complex:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201114511.png"&gt;&lt;img title="Figure 4" src="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201114511.png" alt="Figure 4" width="160" height="62" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Double clicking it opens the file in your preferred IDE and points your cursor directly to the method. If you're interesting to have different insights, you can also change the metric data type to something else:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201107411.png"&gt;&lt;img title="Figure 5" src="https://www-preprod.davidhome.net/contentassets/35299a47b8d442dfb348d60119617eb6/screenshot202024-01-23201107411.png" alt="Figure 5" width="160" height="205" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Closure&lt;/h3&gt;
&lt;p&gt;There is also a CI/CD integration that you can install. Obviously, the goal is to obtain those analysis directly from your build pipelines so that you can act preventively on any code quality depreciation. If you're interested, you can also view certain sample reports at the following URL:&amp;nbsp;&lt;a href="https://www.ndepend.com/sample-reports/"&gt;https://www.ndepend.com/sample-reports/&lt;/a&gt;. For Optimizely solutions, CD/CI can come super handy because such projects can quickly become a burden maintainability wise.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Fri, 26 Jan 2024 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/optimizely-content-cloud-cms-apps-add-ons-tips-tricks/</guid><link>https://www-preprod.davidhome.net/blog/optimizely-content-cloud-cms-apps-add-ons-tips-tricks/</link><category>Optimizely</category><title>Optimizely Content Cloud CMS Apps (Add-ons) - Tips &amp; Tricks</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;Developing an Optimizely CMS Add-on can be a bit tricky. There is a lot of advantages of doing it, but building it right can be a bit tedious. You have probably already seen the following&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/developing-add-ons"&gt;documentation page&lt;/a&gt;&amp;nbsp;about the subject itself, but it feels like a lot of details are missing to make it right. You will also quickly realize there is a lot of historical elements which makes the process a bit more complicated/confusing. In this blog, we will unravel everything so that you can successfully build yours!&lt;/p&gt;
&lt;p&gt;I will be assuming that your project is currently using .NET 6.0, but the process is the same if the target framework changes. You can also add conditional dependencies based on the target framework, which then the command&amp;nbsp;&lt;code&gt;dotnet pack&lt;/code&gt;&amp;nbsp;will automatically handle when bundling your library. I will also assume that you are already mastering how packing your library to .nupkg file(s) works.&lt;/p&gt;
&lt;h2&gt;1. Adjustements inside the project file&lt;/h2&gt;
&lt;p&gt;First of all, create a new solution with a library project using your preferred IDE, then edit the .csproj file. Your file should look like the following in the end:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt; &lt;span class="hljs-attr"&gt;Sdk&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Microsoft.NET.Sdk.Razor"&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;TargetFramework&lt;/span&gt;&amp;gt;&lt;/span&gt;net6.0&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;TargetFramework&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Nullable&lt;/span&gt;&amp;gt;&lt;/span&gt;enable&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Nullable&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ImplicitUsings&lt;/span&gt;&amp;gt;&lt;/span&gt;enable&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ImplicitUsings&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;AddRazorSupportForMvc&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;AddRazorSupportForMvc&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;FrameworkReference&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Microsoft.AspNetCore.App"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
  
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
		&lt;span class="hljs-comment"&gt;&amp;lt;!--    These are package example. Install those you need. --&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageReference&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.AspNetCore.Mvc"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageReference&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.Core"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageReference&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.UI.Core"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Content&lt;/span&gt; &lt;span class="hljs-attr"&gt;Remove&lt;/span&gt;=&lt;span class="hljs-string"&gt;"module.config"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;None&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"module.config"&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;CopyToOutputDirectory&lt;/span&gt;&amp;gt;&lt;/span&gt;Never&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;CopyToOutputDirectory&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;None&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Content&lt;/span&gt; &lt;span class="hljs-attr"&gt;Remove&lt;/span&gt;=&lt;span class="hljs-string"&gt;"packages.lock.json"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;None&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"packages.lock.json"&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;CopyToOutputDirectory&lt;/span&gt;&amp;gt;&lt;/span&gt;Never&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;CopyToOutputDirectory&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;None&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can also realize, I'm using&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management"&gt;CPM&lt;/a&gt;. This will greatly simplify the dependency management along the way.&lt;/p&gt;
&lt;p&gt;Couple of things to highlight here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Super important to change the SDK attribute on the "Project" node to "Microsoft.NET.Sdk.Razor".&lt;/li&gt;
&lt;li&gt;Inside the first PropertyGroup node, add AddRazorSupportForMvc and set it "true".&lt;/li&gt;
&lt;li&gt;Make sure to add a FrameworkReference node which will be including the Microsoft.AspNetCore.App reference. Necessary for having the ASP.NET Core web references, which normally the SDK Microsoft.NET.Sdk.Web includes by default.&lt;/li&gt;
&lt;li&gt;The last, but not least, make sure to remove &amp;amp; ignore any files you want to exclude from your .nupkg file. In our example, and you will probably need it too, we want to prevent both "module.config" and "packages.lock.json" file to be inside the file package.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Custom MSBuild instructions&lt;/h2&gt;
&lt;p&gt;There is a couple of adjustments to make so that MSBuild is helping up ease the bundling:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Assuming your projects are hierarchically structured in the following directory pattern: [root]/src/projectname/projectname.csproj, create a Directory.Build.props file in the "src" folder with the following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;NoWarn&lt;/span&gt;&amp;gt;&lt;/span&gt;NU1507&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;NoWarn&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;RestorePackagesWithLockFile&lt;/span&gt;&amp;gt;&lt;/span&gt;True&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;RestorePackagesWithLockFile&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Assuming the same structure, create under the "src" folder the file Directory.Packages.props with the following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ManagePackageVersionsCentrally&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ManagePackageVersionsCentrally&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;CentralPackageTransitivePinningEnabled&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;CentralPackageTransitivePinningEnabled&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
		&lt;span class="hljs-comment"&gt;&amp;lt;!--    These are package example. Install those you need. --&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageVersion&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.AspNetCore.Mvc"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Version&lt;/span&gt;=&lt;span class="hljs-string"&gt;"[12.4.0, 13.0.0)"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageVersion&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.Core"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Version&lt;/span&gt;=&lt;span class="hljs-string"&gt;"[12.4.0, 13.0.0)"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageVersion&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServer.CMS.UI.Core"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Version&lt;/span&gt;=&lt;span class="hljs-string"&gt;"[12.4.0, 13.0.0)"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Inside the project folder, where the .csproj file resides, create a new file entitled Directory.Build.props with the following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version=&lt;span class="hljs-string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="hljs-string"&gt;"utf-8"&lt;/span&gt; ?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt; &lt;span class="hljs-attr"&gt;xmlns&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ClientResources&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(ProjectDir)ClientResources\**\*"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;TmpOutDir&lt;/span&gt;&amp;gt;&lt;/span&gt;$([System.IO.Path]::Combine($(ProjectDir), 'tmp'))&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;TmpOutDir&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;NoWarn&lt;/span&gt;&amp;gt;&lt;/span&gt;NU1507&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;NoWarn&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;RestorePackagesWithLockFile&lt;/span&gt;&amp;gt;&lt;/span&gt;True&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;RestorePackagesWithLockFile&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PropertyGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Content&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(MSBuildProjectName).zip"&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Pack&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Pack&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackagePath&lt;/span&gt;&amp;gt;&lt;/span&gt;contentFiles\any\any\modules\_protected\$(MSBuildProjectName)&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PackagePath&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;BuildAction&lt;/span&gt;&amp;gt;&lt;/span&gt;None&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;BuildAction&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackageCopyToOutput&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PackageCopyToOutput&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Content&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Content&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"msbuild\CopyZipFiles.targets"&lt;/span&gt; &amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Pack&lt;/span&gt;&amp;gt;&lt;/span&gt;true&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Pack&lt;/span&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;PackagePath&lt;/span&gt;&amp;gt;&lt;/span&gt;build\$(MSBuildProjectName).targets&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;PackagePath&lt;/span&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Content&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Still in the project folder, create the file Directory.Build.targets and add the following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version=&lt;span class="hljs-string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="hljs-string"&gt;"utf-8"&lt;/span&gt; ?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt; &lt;span class="hljs-attr"&gt;xmlns&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Target&lt;/span&gt; &lt;span class="hljs-attr"&gt;Name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CreateCmsAddOnZip"&lt;/span&gt; &lt;span class="hljs-attr"&gt;BeforeTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Build"&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Copy&lt;/span&gt; &lt;span class="hljs-attr"&gt;SourceFiles&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(ProjectDir)module.config"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DestinationFolder&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(TmpOutDir)\content"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Copy&lt;/span&gt; &lt;span class="hljs-attr"&gt;SourceFiles&lt;/span&gt;=&lt;span class="hljs-string"&gt;"@(ClientResources)"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DestinationFiles&lt;/span&gt;=&lt;span class="hljs-string"&gt;"@(ClientResources -&amp;gt; '$(TmpOutDir)\content\$(PackageVersion)\ClientResources\%(RecursiveDir)%(Filename)%(Extension)')"&lt;/span&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="hljs-comment"&gt;&amp;lt;!-- Update the module config with the version information --&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;XmlPoke&lt;/span&gt; &lt;span class="hljs-attr"&gt;XmlInputPath&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(TmpOutDir)\content\module.config"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Query&lt;/span&gt;=&lt;span class="hljs-string"&gt;"/module/@clientResourceRelativePath"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Value&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(PackageVersion)"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Target&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Target&lt;/span&gt; &lt;span class="hljs-attr"&gt;Name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"ZipClientResources"&lt;/span&gt; &lt;span class="hljs-attr"&gt;BeforeTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Build"&lt;/span&gt; &lt;span class="hljs-attr"&gt;AfterTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CreateCmsAddOnZip"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DependsOnTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CreateCmsAddOnZip"&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ZipDirectory&lt;/span&gt; &lt;span class="hljs-attr"&gt;SourceDirectory&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(TmpOutDir)\content"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DestinationFile&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(ProjectDir)$(MSBuildProjectName).zip"&lt;/span&gt; &lt;span class="hljs-attr"&gt;Overwrite&lt;/span&gt;=&lt;span class="hljs-string"&gt;"true"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Target&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Target&lt;/span&gt; &lt;span class="hljs-attr"&gt;Name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CleanupTmpOutDir"&lt;/span&gt; &lt;span class="hljs-attr"&gt;BeforeTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Build"&lt;/span&gt; &lt;span class="hljs-attr"&gt;AfterTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"ZipClientResources"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DependsOnTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"ZipClientResources"&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;RemoveDir&lt;/span&gt; &lt;span class="hljs-attr"&gt;Directories&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(TmpOutDir)"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Target&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="5"&gt;
&lt;li&gt;You will also have to add the file CopyZipFiles.targets to a new "msbuild" folder under the root of the project directory:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version=&lt;span class="hljs-string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="hljs-string"&gt;"utf-8"&lt;/span&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Project&lt;/span&gt; &lt;span class="hljs-attr"&gt;xmlns&lt;/span&gt;=&lt;span class="hljs-string"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt; &lt;span class="hljs-attr"&gt;ToolsVersion&lt;/span&gt;=&lt;span class="hljs-string"&gt;"4.0"&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;CmsAddOnZips&lt;/span&gt; &lt;span class="hljs-attr"&gt;Include&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(MSBuildThisFileDirectory)..\contentFiles\any\any\modules\_protected\**\*.zip"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Target&lt;/span&gt; &lt;span class="hljs-attr"&gt;Name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CopyCmsAddOnZip"&lt;/span&gt; &lt;span class="hljs-attr"&gt;BeforeTargets&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Build"&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;Copy&lt;/span&gt; &lt;span class="hljs-attr"&gt;SourceFiles&lt;/span&gt;=&lt;span class="hljs-string"&gt;"@(CmsAddOnZips)"&lt;/span&gt; &lt;span class="hljs-attr"&gt;DestinationFolder&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$(MSBuildProjectDirectory)\modules\_protected\%(RecursiveDir)"&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Target&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;Project&lt;/span&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To summarize, these customizations will do the following to your project each time you will be compiling:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adds CPM, as previously mentioned.&lt;/li&gt;
&lt;li&gt;Adds NuGet lock files, super useful for your DevOps pipeline.&lt;/li&gt;
&lt;li&gt;Automatically compile views.&lt;/li&gt;
&lt;li&gt;Automatically generates module the zip file with all necessary elements in it that will be packaged within your .nupkg file.
&lt;ul&gt;
&lt;li&gt;This file contains the recommended structure &amp;amp; content that you can find in the&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/developing-add-ons"&gt;Optimizely CMS addon documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3. Minimum required configuration&lt;/h2&gt;
&lt;p&gt;See the "module.config" file like the definition of your addon. Without it, Optimizely will use the default values from the class ShellModuleManifest, which unfortunately omit a very important detail; The assembly&amp;rsquo;s name of your addon. Without it, Optimizely won't be able to load yours at boot. Create it where the .csproj file resides with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-meta"&gt;&amp;lt;?xml version=&lt;span class="hljs-string"&gt;"1.0"&lt;/span&gt; encoding=&lt;span class="hljs-string"&gt;"utf-8"&lt;/span&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;module&lt;/span&gt; &lt;span class="hljs-attr"&gt;loadFromBin&lt;/span&gt;=&lt;span class="hljs-string"&gt;"false"&lt;/span&gt; &lt;span class="hljs-attr"&gt;name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Your.Assembly.Name"&lt;/span&gt; &lt;span class="hljs-attr"&gt;viewEngine&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Razor"&lt;/span&gt; &lt;span class="hljs-attr"&gt;clientResourceRelativePath&lt;/span&gt;=&lt;span class="hljs-string"&gt;"$version$"&lt;/span&gt; &lt;span class="hljs-attr"&gt;tags&lt;/span&gt;=&lt;span class="hljs-string"&gt;"EPiServerModulePackage"&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;assemblies&lt;/span&gt;&amp;gt;&lt;/span&gt;
		&lt;span class="hljs-comment"&gt;&amp;lt;!-- Change the assembly name with yours --&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;assembly&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Your.Assembly.Name"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;assemblies&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;clientModule&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;moduleDependencies&lt;/span&gt;&amp;gt;&lt;/span&gt;
			&lt;span class="hljs-comment"&gt;&amp;lt;!-- Adjust accordingly --&amp;gt;&lt;/span&gt;
      &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;dependency&lt;/span&gt;=&lt;span class="hljs-string"&gt;"CMS"&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"RunAfter"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;moduleDependencies&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;clientModule&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;module&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add an extension method on the interface "IServiceCollection" and add at least the following piece of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; IServiceCollection &lt;span class="hljs-title"&gt;AddMyAddon&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;span class="hljs-keyword"&gt;this&lt;/span&gt; IServiceCollection services&lt;/span&gt;)&lt;/span&gt;
    {
        &lt;span class="hljs-comment"&gt;// Add services here.&lt;/span&gt;
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; services
                &lt;span class="hljs-comment"&gt;// Super required, otherwise your addon won't load when the site loads.&lt;/span&gt;
            .Configure&amp;lt;ProtectedModuleOptions&amp;gt;(
            pm =&amp;gt;
            {
                &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!pm.Items.Any(i =&amp;gt; i.Name.Equals(ModuleName, StringComparison.OrdinalIgnoreCase)))
                {
                    pm.Items.Add(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; ModuleDetails { Name = ModuleName });
                }
            });
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Controllers &amp;amp; Views&lt;/h2&gt;
&lt;p&gt;Probably the most undocumented/unclear part for Optimizely CMS addons. The instructions around views doesn't exactly explain how they can be used or how the routing works within your library. If you're looking at existing addons, e.g.,&amp;nbsp;&lt;a href="https://github.com/Geta/geta-notfoundhandler"&gt;Geta.NotFoundHandler&lt;/a&gt;, the majority of developers are handling it slightly differently.&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/migrating-add-ons-to-net-5"&gt;This page&lt;/a&gt;&amp;nbsp;explains how to structure your files in a manner that the module will automatically include them for you, but I was unable to make it work. Maybe because it should be exclusively a&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-8.0&amp;amp;tabs=visual-studio"&gt;razor page&lt;/a&gt;&amp;nbsp;and not a simple razor view dependent on a Controller. Fortunately, you can make it work with a controller, but with a little additional tweaking:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-8.0#custom-route-attributes-using-iroutetemplateprovider"&gt;Route Attribute&lt;/a&gt;. We will use it to customize the routes to our controllers. Here's an example:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; EPiServer.Shell;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc.Routing;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Playground.Mvc&lt;/span&gt;;

[&lt;span class="hljs-meta"&gt;AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)&lt;/span&gt;]
&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ModuleRoute&lt;/span&gt; : &lt;span class="hljs-title"&gt;Attribute&lt;/span&gt;, &lt;span class="hljs-title"&gt;IRouteTemplateProvider&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;readonly&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; _controllerName;
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;readonly&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; _actionName;

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; Template =&amp;gt; Paths.ToResource(&lt;span class="hljs-keyword"&gt;typeof&lt;/span&gt;(ModuleRoute), &lt;span class="hljs-string"&gt;$"&lt;span class="hljs-subst"&gt;{_controllerName}&lt;/span&gt;/&lt;span class="hljs-subst"&gt;{_actionName}&lt;/span&gt;"&lt;/span&gt;);
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;int&lt;/span&gt;? Order { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; } = &lt;span class="hljs-number"&gt;0&lt;/span&gt;;
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; Name { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }

    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-title"&gt;ModuleRoute&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt; controllerName, &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; actionName&lt;/span&gt;)&lt;/span&gt;
    {
        _controllerName = controllerName;
        _actionName = actionName;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Decorate your controller actions with your newly created route attribute. e.g.:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;    [&lt;span class="hljs-meta"&gt;HttpGet&lt;/span&gt;]
    [&lt;span class="hljs-meta"&gt;ModuleRoute(&lt;span class="hljs-string"&gt;"Default"&lt;/span&gt;, &lt;span class="hljs-string"&gt;"Index"&lt;/span&gt;)&lt;/span&gt;]
    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; IActionResult &lt;span class="hljs-title"&gt;Index&lt;/span&gt;()&lt;/span&gt;
    {
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; View();
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Create a new menu provider class including all paths to your actions in your addons. e.g.:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; EPiServer.Framework.Localization;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; EPiServer.Shell;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; EPiServer.Shell.Navigation;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Playground.Controllers;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;Playground.Optimizely&lt;/span&gt;;

[&lt;span class="hljs-meta"&gt;MenuProvider&lt;/span&gt;]
&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;AddonMenuProvider&lt;/span&gt; : &lt;span class="hljs-title"&gt;IMenuProvider&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;readonly&lt;/span&gt; LocalizationService _localizationService;

    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-title"&gt;AddonMenuProvider&lt;/span&gt;(&lt;span class="hljs-params"&gt;LocalizationService localizationService&lt;/span&gt;)&lt;/span&gt;
    {
        _localizationService = localizationService;
    }

    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; IEnumerable&amp;lt;MenuItem&amp;gt; &lt;span class="hljs-title"&gt;GetMenuItems&lt;/span&gt;()&lt;/span&gt;
    {
        &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;yield&lt;/span&gt; &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title"&gt;UrlMenuItem&lt;/span&gt;(&lt;span class="hljs-params"&gt;_localizationService.GetString(&lt;span class="hljs-string"&gt;"/myaddon/gadget/title"&lt;/span&gt;, &lt;span class="hljs-string"&gt;"My Addon"&lt;/span&gt;&lt;/span&gt;), "/&lt;span class="hljs-keyword"&gt;global&lt;/span&gt;/cms/myaddon",
            Paths.&lt;span class="hljs-title"&gt;ToResource&lt;/span&gt;(&lt;span class="hljs-params"&gt;GetType(&lt;/span&gt;), $"Default/&lt;/span&gt;{&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(DefaultController.Index)}&lt;span class="hljs-string"&gt;"))
        {
            SortIndex = 0,
            Alignment = 0,
            IsAvailable = _ =&amp;gt; true
        };

        yield return new UrlMenuItem(_localizationService.GetString("&lt;/span&gt;/myaddon/index/menu&lt;span class="hljs-string"&gt;", "&lt;/span&gt;Home&lt;span class="hljs-string"&gt;"), "&lt;/span&gt;/&lt;span class="hljs-keyword"&gt;global&lt;/span&gt;/cms/myaddon/index&lt;span class="hljs-string"&gt;",
            Paths.ToResource(GetType(), $"&lt;/span&gt;Default/{&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(DefaultController.Index)}&lt;span class="hljs-string"&gt;"))
        {
            SortIndex = 10,
            Alignment = 0,
            IsAvailable = _ =&amp;gt; true
        };

        yield return new UrlMenuItem(_localizationService.GetString("&lt;/span&gt;/myaddon/secondaction/menu&lt;span class="hljs-string"&gt;", "&lt;/span&gt;Second Action&lt;span class="hljs-string"&gt;"), "&lt;/span&gt;/&lt;span class="hljs-keyword"&gt;global&lt;/span&gt;/cms/myaddon/secondaction&lt;span class="hljs-string"&gt;",
            Paths.ToResource(GetType(), $"&lt;/span&gt;Default/{&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(DefaultController.SecondAction)}&lt;span class="hljs-string"&gt;"))
        {
            SortIndex = 20,
            Alignment = 0,
            IsAvailable = _ =&amp;gt; true
        };
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By doing so, you are generating routes which will point directly on your addon controller. Say by example you have the "DefaultController" class with the "Index" action, well then, the action under your browser should look as the following: ~/EPiServer/MyAddon/Default/Index. This also convenientely allow you to use razor helpers such as Html.BeginForm, since the MVC routing system knows these belongs to your addon with a custom template.&lt;/p&gt;
&lt;p&gt;It is very important to respect the actions defined within your IMenuProvider implementation, otherwise the custom routing attribute won't be working just right. As you can see, a certain minimum level of structure has been established in this file, which makes actions from the controller to work correctly. The third parameter of the constructor of UrlMenuItem is exactly where the magic happens. The recommendation is indeed to keep using the helper, Paths.ToResource and add the additional segment manually. This consequently creates the same path as previously described in the last paragraph and will match it with the available controller action. A menu item is essentially an element that will appear under the Backoffice, when navigating under ~/EPiServer.&lt;/p&gt;
&lt;p&gt;I hope this will be super helpful to people reading this blog! You can view a starter code example over there:&amp;nbsp;&lt;a href="https://github.com/ddprince17/Optimizely-CMS-Addon-Playground"&gt;https://github.com/ddprince17/Optimizely-CMS-Addon-Playground&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Mon, 22 Jan 2024 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/secure-your-smtp-configuration-configuration-builders/</guid><link>https://www-preprod.davidhome.net/blog/secure-your-smtp-configuration-configuration-builders/</link><category>Optimizely</category><title>Secure your SMTP configuration - Configuration Builders</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;In .NET Framework, we are all aware of the famous&amp;nbsp;&lt;code&gt;&amp;lt;network&amp;gt;&lt;/code&gt;&amp;nbsp;node under the Web.config file. This has been the facto way to configure your SMTP settings since, well, a long time. In .NET, the story is quite different, as the approach is way cleaner as it previously was, there is no enforced structure or way to configure your settings, you decide, by yourself, how you would like these settings to exist within, either your appsetting.json file or any configuration provider you might have added to your solution.&lt;/p&gt;
&lt;p&gt;For example, under a Optimizely CMS 12 and higher solution, you would have to add configuration&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/digital-experience-platform/v1.2.0-dxp-cloud-services/docs/configuring-the-email-server"&gt;under the "EPiServer.Cms.Smtp.Network" section&lt;/a&gt;. But in the end, this structure is something established by Optimizely, nothing prevents you to add them elsewhere (even though I would not recommend to duplicate configuration under multiple different nodes).&lt;/p&gt;
&lt;p&gt;In previous version of Optimizely (lower than version 12), since we're using .NET Framework, we must use the Web.config structure to be able to have our SMTP settings properly setup. The thing though is that we had to put the password cleartext right inside this section. There was also no way to use configuration transformation as it usually works with the appSettings and connectionStrings sections. Until we found a way to move those settings inside the appSettings section.&lt;/p&gt;
&lt;p&gt;We deploy our solutions to DXP. So naturally speaking, we have no control over the infrastructure where the application is hosted, but we have a way, just before sending the package to Optimizely, to transform the configuration. Under Azure DevOps, there's a task,&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/file-transform-v2?view=azure-pipelines"&gt;File Transform v2&lt;/a&gt;, which does exactly what we want. We store our secrests inside&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups?view=azure-devops&amp;amp;tabs=yaml"&gt;variable groups&lt;/a&gt;&amp;nbsp;and let the configuration transformation task to do its magic (variable substitution). So, this step covers like 90% of our cases, we can safely store secrets, but unfortunately, like I said, this only works for the appSettings section. What about the smtp section then? I'd tell you "&lt;a href="https://learn.microsoft.com/en-us/aspnet/config-builder"&gt;Configuration builders&lt;/a&gt;". There are already existing config builders in the wild, the one we use the most is the AzureKeyVaultConfigBuilder, but mainly for development purpose, as we still don't want to expose secrets, even for that.&lt;/p&gt;
&lt;p&gt;But I can hear ya, like at a 100 miles, with your question: "But Dave, what does this change for the SMTP configuration?" I'm glad you're asking! See, you can add&amp;nbsp;&lt;em&gt;existing&lt;/em&gt;&amp;nbsp;config builder, but you can also&amp;nbsp;&lt;strong&gt;create&lt;/strong&gt;&amp;nbsp;a new one. So, since I want the SMTP configuration to be loaded from the appSettings, I'll create a class that will do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;SmtpConfigurationBuilder&lt;/span&gt; : &lt;span class="hljs-title"&gt;ConfigurationBuilder&lt;/span&gt;
    {
        &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;override&lt;/span&gt; ConfigurationSection &lt;span class="hljs-title"&gt;ProcessConfigurationSection&lt;/span&gt;(&lt;span class="hljs-params"&gt;ConfigurationSection configSection&lt;/span&gt;)&lt;/span&gt;
        {
            &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!(configSection &lt;span class="hljs-keyword"&gt;is&lt;/span&gt; SmtpSection smtpSection) || smtpSection.Network == &lt;span class="hljs-literal"&gt;null&lt;/span&gt;) 
                &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;base&lt;/span&gt;.ProcessConfigurationSection(configSection);

            smtpSection.Network.Host = ConfigurationManager.AppSettings[&lt;span class="hljs-string"&gt;"smtp-host"&lt;/span&gt;];
            smtpSection.Network.UserName = ConfigurationManager.AppSettings[&lt;span class="hljs-string"&gt;"smtp-userName"&lt;/span&gt;];
            smtpSection.Network.Password = ConfigurationManager.AppSettings[&lt;span class="hljs-string"&gt;"smtp-password"&lt;/span&gt;];
            smtpSection.Network.Port = ConfigurationManager.AppSettings[&lt;span class="hljs-string"&gt;"smtp-port"&lt;/span&gt;].ToInt(defaultValue: &lt;span class="hljs-number"&gt;587&lt;/span&gt;);
            smtpSection.Network.EnableSsl = ConfigurationManager.AppSettings[&lt;span class="hljs-string"&gt;"smtp-enableSsl"&lt;/span&gt;].ToBoolean();

            &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;base&lt;/span&gt;.ProcessConfigurationSection(configSection);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will instruct whatever that uses my config builder to execute only when the current section is a SMTP section and that the Network node isn't null. To have that work under your Web.config, you'll have to add three additional items:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Look for the "configSections" node and add the following at the end:
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;section&lt;/span&gt; &lt;span class="hljs-attr"&gt;name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"configBuilders"&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"&lt;/span&gt; &lt;span class="hljs-attr"&gt;restartOnExternalChanges&lt;/span&gt;=&lt;span class="hljs-string"&gt;"false"&lt;/span&gt; &lt;span class="hljs-attr"&gt;requirePermission&lt;/span&gt;=&lt;span class="hljs-string"&gt;"false"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Just after the "configSections" node, add the following:
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;configBuilders&lt;/span&gt;&amp;gt;&lt;/span&gt;
  	&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;builders&lt;/span&gt;&amp;gt;&lt;/span&gt;
  		&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;name&lt;/span&gt;=&lt;span class="hljs-string"&gt;"SmtpConfigurationBuilder"&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Full.Namespace.Of.My.Class.SmtpConfigurationBuilder, Assembly.Name"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  	&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;builders&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;configBuilders&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
If you have multiple config builder, take a good attention of the ordering, as I think it has importance when they are loaded.&lt;/li&gt;
&lt;li&gt;And finally, replace your SMTP node with something like so:
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;  &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;system.net&lt;/span&gt;&amp;gt;&lt;/span&gt;
  	&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;mailSettings&lt;/span&gt;&amp;gt;&lt;/span&gt;
  		&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;smtp&lt;/span&gt; &lt;span class="hljs-attr"&gt;from&lt;/span&gt;=&lt;span class="hljs-string"&gt;"donotreply@example.com"&lt;/span&gt; &lt;span class="hljs-attr"&gt;configBuilders&lt;/span&gt;=&lt;span class="hljs-string"&gt;"SmtpConfigurationBuilder"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
  	&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;mailSettings&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;system.net&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, with that, you have a way to load all you SMTP configuration directly from your appSettings. Isn't that great? Assuming you're using Azure DevOps to build and deploy your solutions, you can now easily leverage the variable substitution to change your SMTP settings just before sending the package to Optimizely for a deployment.&lt;/p&gt;
&lt;p&gt;Another thing to point out, as you can see, you can add multiple config builders. We do that on our side, only for our development environments, to protect any sensitive information, by using the AzureKeyVaultConfigBuilder. This setup can work in pair, meaning that, say, you have both AzureKeyVaultConfigBuilder and the SmtpConfigurationBuilder, you can use the first config builder to load the secrets from your key vault, then ask your SMTP builder to load these for your SMTP configuration. It can be extremely useful for a lot of different applications.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Sun, 21 Jan 2024 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/idx21323-requirenonce-is-true-nonce-was-null/</guid><link>https://www-preprod.davidhome.net/blog/idx21323-requirenonce-is-true-nonce-was-null/</link><category>Optimizely</category><title>IDX21323 - RequireNonce is true, Nonce was null</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;We have multiple clients configured with Azure Active Directory (Microsoft Entra) for requiring authentication when accessing their website. The majority of them is only for protecting the backoffice (CMS admin), but we have certain of them that uses it for protecting the whole site.&lt;/p&gt;
&lt;p&gt;There is&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-management-system/docs/integrate-azure-ad-using-openid-connect"&gt;existing Optimizely documentation&lt;/a&gt;&amp;nbsp;that allow you to setup your site to use AAD authentication, but it doesn't talks about the&amp;nbsp;&lt;em&gt;famous&lt;/em&gt;&amp;nbsp;issue certain people are having when signing in to the website; "IDX21323 - RequireNonce is true, Nonce was null". I might come with a surprise, but this issue will be occurring systematically under all DXP environments if you do not ask Optimizely to change a setting under Cloudflare for you. The problem is described there:&amp;nbsp;&lt;a href="https://support.optimizely.com/hc/en-us/articles/7492181419789-Nonce-Error-Using-OpenIdConnect-for-Authentication-with-DXC"&gt;https://support.optimizely.com/hc/en-us/articles/7492181419789-Nonce-Error-Using-OpenIdConnect-for-Authentication-with-DXC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The solution though is something that doesn't seems to ring a bell for certain people. As of the time of writing, it says "to bypass the origin cache control", but in contrary, the mentioned page rule is&amp;nbsp;&lt;strong&gt;enabling&lt;/strong&gt;&amp;nbsp;Cloudflare to respect the origin cache control header. You see, it is&amp;nbsp;&lt;a href="https://community.cloudflare.com/t/cloudflare-dont-respect-origin-cache-control-header/244056"&gt;not entirely respecting cache-control directive from the origin server,&amp;nbsp;&lt;strong&gt;by default&lt;/strong&gt;&lt;/a&gt;. Organizations with an enterprise plan with Cloudflare are all having the rule disabled by default. Something which is hard to understand as Cloudflare's documentation doesn't really give up insights on that matter except in the previous thread. It means that even if you've decided to put any custom cache-control directives within your application, Cloudflare&amp;nbsp;&lt;a href="https://developers.cloudflare.com/cache/about/cache-control/#origin-cache-control-behavior"&gt;will ignore some of them or will behave differently&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This default setup can cause a couple of different issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Signing in under certain URLs/paths, the Nonce cookie isn't setup during the authentication, thus causing the IDX21323 issue.&lt;/li&gt;
&lt;li&gt;Leaving certain assets "publicly" available after someone has loaded it once, even though it should require authentication, because it isn't validating it once the Edge has cached the asset.&lt;/li&gt;
&lt;li&gt;Breaking images because the Edge has cached the authentication redirection action, a http 302 status code.&lt;/li&gt;
&lt;li&gt;Preventing your application to set request cookies. Cloudflare will sometimes, under specific scenarios, completely discard the cookie before sending the response to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't get me wrong, caching is important, but you need to adapt certain rules depending on what your website is. For intranet sites by example, it's important that the caching doesn't break the experience nor expose certain privately accessible assets. There's a couple of tricks you can apply right up front, by example, when using the extension method&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.openidconnectextensions.addopenidconnect?view=aspnetcore-6.0#microsoft-extensions-dependencyinjection-openidconnectextensions-addopenidconnect(microsoft-aspnetcore-authentication-authenticationbuilder-system-action((microsoft-aspnetcore-authentication-openidconnect-openidconnectoptions)))"&gt;AddOpenIdConnect&lt;/a&gt;, you can control the Cache-Control header&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectevents.onredirecttoidentityprovider?view=aspnetcore-6.0#microsoft-aspnetcore-authentication-openidconnect-openidconnectevents-onredirecttoidentityprovider"&gt;only when redirecting to the authentication provider&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All in all, though, take a good attention of how your website is behaving when hosted under Optimizely DXP, especially when you are protecting certain areas, more than the CMS backoffice itself. Normally any Optimizely CMS solution will include right out of the box optimized cache control headers, especially for assets, but keep in mind that Cloudflare will react differently with them since the page rule "Origin Cache-Control" is not enabled by default (for enterprise customers only).&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Sun, 01 Oct 2023 04:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/optimizely-commerce-business-foundation-custom-entity-model-to-add-on-quan-mai-s-blog/</guid><link>https://www-preprod.davidhome.net/blog/optimizely-commerce-business-foundation-custom-entity-model-to-add-on-quan-mai-s-blog/</link><category>Optimizely</category><title>Optimizely Commerce Business Foundation custom entity model - To add on Quan Mai's blog :)</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;&lt;a href="https://vimvq1987.com/loading-the-contacts-organizations-the-right-way/"&gt;Quan Mai is right&lt;/a&gt;, there is another official way to load organizations and contacts under Optimizely Commerce using the famous static class&amp;nbsp;&lt;code&gt;BusinessManager&lt;/code&gt;. Though, there's more you can do about it. Have you ever wonder if you could personalize the data access on custom properties using your own class instead of using, e.g.,&amp;nbsp;&lt;code&gt;Organization.Properties["MyCustomProperty"]&lt;/code&gt;? Yes, me too, and in this blog post, I'm going to show you it's&amp;nbsp;&lt;strong&gt;possible&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;For readability simplicity, I'll concentrate the discussion around the organizations, but keep in mind the same personalization principle applies for all other Business Foundation entities. Our journey will start under a configuration file;&amp;nbsp;&lt;code&gt;baf.data.manager.config&lt;/code&gt;. This file lists a couple of Business Foundation entities with their associated&amp;nbsp;&lt;code&gt;RequestHandler&lt;/code&gt;. You see here, the default configuration for an&amp;nbsp;&lt;code&gt;Organization&lt;/code&gt;&amp;nbsp;is the following type:&amp;nbsp;&lt;code&gt;Mediachase.Commerce.Customers.Handlers.OrganizationRequestHandler, Mediachase.Commerce&lt;/code&gt;. If you have Jetbains Rider, you can easily find the class by searching everywhere, usually CTRL + T with the Visual Studio key binding and include non solution items. For those who are using dotPeek by example, you need to decompile the dll&amp;nbsp;&lt;code&gt;Mediachase.Commerce.dll&lt;/code&gt;&amp;nbsp;which is under the NuGet package&amp;nbsp;&lt;code&gt;EPiServer.Commerce.Core&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The&amp;nbsp;&lt;code&gt;OrganizationRequestHandler&lt;/code&gt;&amp;nbsp;inherits&amp;nbsp;&lt;code&gt;CustomerRequestHandlerBase&lt;/code&gt;&amp;nbsp;and this is where we'll be finding what we're looking for. There's a method&amp;nbsp;&lt;code&gt;CreateEntityObject&lt;/code&gt;, which has the following one liner:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; CustomerEntityFactory().Create&amp;lt;EntityObject&amp;gt;((&lt;span class="hljs-built_in"&gt;object&lt;/span&gt;) &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; KeyValuePair&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;, PrimaryKeyId?&amp;gt;(metaClassName, primaryKeyId));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look closely, inside the class&amp;nbsp;&lt;code&gt;CustomerEntityFactory&lt;/code&gt;, you can find how Business Foundation is creating its instance of classes that it will be returning to you after pulling the data. This is also why you can use "OfType" when using&amp;nbsp;&lt;code&gt;BusinessManager&lt;/code&gt;. The class is quite basic, for any known entity, use the following new instance of class to build my entity:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;EntityObject IFactoryMethod&amp;lt;EntityObject&amp;gt;.Create(&lt;span class="hljs-built_in"&gt;object&lt;/span&gt; obj)
    {
      KeyValuePair&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;, PrimaryKeyId?&amp;gt; keyValuePair = obj != &lt;span class="hljs-literal"&gt;null&lt;/span&gt; ? (KeyValuePair&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;, PrimaryKeyId?&amp;gt;) obj : &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; ArgumentNullException(&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt; (obj));
      EntityObject entityObject = (EntityObject) &lt;span class="hljs-literal"&gt;null&lt;/span&gt;;
      &lt;span class="hljs-keyword"&gt;switch&lt;/span&gt; (keyValuePair.Key)
      {
        &lt;span class="hljs-keyword"&gt;case&lt;/span&gt; &lt;span class="hljs-string"&gt;"Contact"&lt;/span&gt;:
          entityObject = (EntityObject) CustomerContact.CreateInstance();
          &lt;span class="hljs-keyword"&gt;break&lt;/span&gt;;
        &lt;span class="hljs-keyword"&gt;case&lt;/span&gt; &lt;span class="hljs-string"&gt;"Address"&lt;/span&gt;:
          entityObject = (EntityObject) CustomerAddress.CreateInstance();
          &lt;span class="hljs-keyword"&gt;break&lt;/span&gt;;
        &lt;span class="hljs-keyword"&gt;case&lt;/span&gt; &lt;span class="hljs-string"&gt;"Organization"&lt;/span&gt;:
          entityObject = (EntityObject) Organization.CreateInstance();
          &lt;span class="hljs-keyword"&gt;break&lt;/span&gt;;
        &lt;span class="hljs-keyword"&gt;case&lt;/span&gt; &lt;span class="hljs-string"&gt;"CreditCard"&lt;/span&gt;:
          entityObject = (EntityObject) CreditCard.CreateInstance();
          &lt;span class="hljs-keyword"&gt;break&lt;/span&gt;;
      }
      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (entityObject != &lt;span class="hljs-literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; keyValuePair.Value.HasValue)
        entityObject.PrimaryKeyId = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; PrimaryKeyId?(keyValuePair.Value.Value);
      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; entityObject;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Naturally, this was my entry point to personalize how the organizations could be loaded. There are three things you need to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create your own entity.&lt;/li&gt;
&lt;li&gt;Create a new entity factory.&lt;/li&gt;
&lt;li&gt;Create a new request handler.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. Create your own entity&lt;/h3&gt;
&lt;p&gt;We're personalizing our&amp;nbsp;&lt;code&gt;Organization&lt;/code&gt;&amp;nbsp;entity with additional properties, we want them to be accessible without having to call&amp;nbsp;&lt;code&gt;Properties["MyProperty"]&lt;/code&gt;. So here, we'll create a new model, say, MyOrganization, that will be inheriting from&amp;nbsp;&lt;code&gt;Mediachase.Commerce.Customers.Organization&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Data;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.Commerce.Customers;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.Commerce.Customers.Request;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;MyNamespace&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;MyOrganization&lt;/span&gt; : &lt;span class="hljs-title"&gt;Organization&lt;/span&gt;
    {
        &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; MyInternalOrgNumber
        {
            &lt;span class="hljs-keyword"&gt;get&lt;/span&gt; =&amp;gt; (&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;) &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;[&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(MyInternalOrgNumber)];
            &lt;span class="hljs-keyword"&gt;set&lt;/span&gt; =&amp;gt; &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;[&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(MyInternalOrgNumber)] = &lt;span class="hljs-keyword"&gt;value&lt;/span&gt;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the previous example, I want developers to be able to access our custom property "MyInternalOrgNumber" from this object. This is basically the same as calling&amp;nbsp;&lt;code&gt;Organization.Properties["MyInternalOrgNumber"]&lt;/code&gt;. Don't forget from on now, to use your custom class, you need to cast it to "MyOrganization". You can do that in the application layer responsible to obtain data from&amp;nbsp;&lt;code&gt;BusinessManager&lt;/code&gt;. What we did in our project, was to create a service that was the data layer abstraction between our application and Business Foundation. Any calls on the&amp;nbsp;&lt;code&gt;CustomerContext&lt;/code&gt;, e.g.,&amp;nbsp;&lt;code&gt;CustomerContext.Current.GetOrganizations()&lt;/code&gt;&amp;nbsp;can be also casted to "MyOrganization" (as far as I remember&amp;trade;️).&lt;/p&gt;
&lt;h3&gt;2. Create entity factory&lt;/h3&gt;
&lt;p&gt;You still have the class&amp;nbsp;&lt;code&gt;CustomerEntityFactory&lt;/code&gt;&amp;nbsp;decompiled? Go back there and copy it, because it will almost be the same in your implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; MyEntityModelsNamespace;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Common;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Data;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Data.Business;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.Commerce.Customers;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;MyNamespace&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;MyEntityFactory&lt;/span&gt; : &lt;span class="hljs-title"&gt;AbstractFactory&lt;/span&gt;, &lt;span class="hljs-title"&gt;IFactoryMethod&lt;/span&gt;&amp;lt;&lt;span class="hljs-title"&gt;EntityObject&lt;/span&gt;&amp;gt;
    {
        &lt;span class="hljs-comment"&gt;&lt;span class="hljs-doctag"&gt;///&lt;/span&gt; &lt;span class="hljs-doctag"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Creates the specified obj.&lt;span class="hljs-doctag"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class="hljs-comment"&gt;&lt;span class="hljs-doctag"&gt;///&lt;/span&gt; &lt;span class="hljs-doctag"&gt;&amp;lt;param name="obj"&amp;gt;&lt;/span&gt;The obj.&lt;span class="hljs-doctag"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class="hljs-comment"&gt;&lt;span class="hljs-doctag"&gt;///&lt;/span&gt; &lt;span class="hljs-doctag"&gt;&amp;lt;returns&amp;gt;&lt;/span&gt;&lt;span class="hljs-doctag"&gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;/span&gt;
        EntityObject IFactoryMethod&amp;lt;EntityObject&amp;gt;.Create(&lt;span class="hljs-built_in"&gt;object&lt;/span&gt; obj)
        {
            &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; keyValuePair = (KeyValuePair&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;, PrimaryKeyId?&amp;gt;?) obj ?? &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; ArgumentNullException(&lt;span class="hljs-keyword"&gt;nameof&lt;/span&gt;(obj));
            &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; key = keyValuePair.Key;
            EntityObject entityObject = key &lt;span class="hljs-keyword"&gt;switch&lt;/span&gt;
            {
                ContactEntity.ClassName =&amp;gt; CustomerContact.CreateInstance(),
                AddressEntity.ClassName =&amp;gt; CustomerAddress.CreateInstance(),
                OrganizationEntity.ClassName =&amp;gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; MyOrganization(),
                CreditCardEntity.ClassName =&amp;gt; CreditCard.CreateInstance()
                _ =&amp;gt; &lt;span class="hljs-literal"&gt;default&lt;/span&gt;
            };

            &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (entityObject != &lt;span class="hljs-literal"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; keyValuePair.Value.HasValue)
                entityObject.PrimaryKeyId = keyValuePair.Value.Value;

            &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; entityObject;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Create request handler&lt;/h3&gt;
&lt;p&gt;For an organization, as I've previously mentioned, by default it's using&amp;nbsp;&lt;code&gt;Mediachase.Commerce.Customers.Handlers.OrganizationRequestHandler, Mediachase.Commerce&lt;/code&gt;&amp;nbsp;as the type. Our class then, will look like that:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Data;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.BusinessFoundation.Data.Business;
&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; Mediachase.Commerce.Customers.Handlers;

&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; &lt;span class="hljs-title"&gt;MyNamespace&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;MyOrganizationRequestHandler&lt;/span&gt; : &lt;span class="hljs-title"&gt;OrganizationRequestHandler&lt;/span&gt;
    {
        &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;protected&lt;/span&gt; &lt;span class="hljs-keyword"&gt;override&lt;/span&gt; EntityObject &lt;span class="hljs-title"&gt;CreateEntityObject&lt;/span&gt;(&lt;span class="hljs-params"&gt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt; metaClassName, PrimaryKeyId? primaryKeyId&lt;/span&gt;)&lt;/span&gt;
        {
            &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; MyEntityFactory().Create&amp;lt;EntityObject&amp;gt;(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; KeyValuePair&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;, PrimaryKeyId?&amp;gt;(metaClassName, primaryKeyId));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final touch; Change the configuration under the file&amp;nbsp;&lt;code&gt;baf.data.manager.config&lt;/code&gt;&amp;nbsp;to point it to your new request handler. In our example, the file should have the following line instead of the default one for the organization:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;metaClass&lt;/span&gt;=&lt;span class="hljs-string"&gt;"Organization"&lt;/span&gt; &lt;span class="hljs-attr"&gt;method&lt;/span&gt;=&lt;span class="hljs-string"&gt;"*"&lt;/span&gt; &lt;span class="hljs-attr"&gt;type&lt;/span&gt;=&lt;span class="hljs-string"&gt;"MyNamespace.MyOrganizationRequestHandler, MyAssemblyName"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Maybe it might be a bit overkill to do that just to be able to use your own class for the entities, but in the end, it helped a lot. Looking for a reference is easier; we can easily find who is using a certain custom property and whatnot. Also, it enhances a bit the readability. One other potential advantage of the approach would be for new developers, especially junior, usually not accustomed with the certain level of complexity Business Foundation entails.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Sat, 01 Jul 2023 04:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/beware-of-multiple-enumeration-of-ienumerable/</guid><link>https://www-preprod.davidhome.net/blog/beware-of-multiple-enumeration-of-ienumerable/</link><category>Programming</category><title>Beware of multiple enumeration of IEnumerable</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;We've all been there, one day or another, debugging a performance issue in production. It's hard to predict and especially difficult to find when it's our first time. Fortunately, though, there is a lot we can do to prevent such issues and today we'll talk about the&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;interface in C#.&lt;/p&gt;
&lt;p&gt;If you're using ReSharper or Rider from JetBrains, you're probably already aware of the famous warning: "&lt;a href="https://www.jetbrains.com/help/resharper/PossibleMultipleEnumeration.html"&gt;Possible multiple enumeration of IEnumerable&lt;/a&gt;". Microsoft did&amp;nbsp;&lt;em&gt;finally&lt;/em&gt;&amp;nbsp;also implemented the warning:&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1851"&gt;https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1851&lt;/a&gt;. It's something, I would say, a lot of developers are easily overlooking. Though, indeed, like mentioned inside JetBrains' article, not every warning, in a sense, are truly by definition a mark of something is wrong in the code. It's a reminder that it shouldn't be ignored and be taken seriously.&lt;/p&gt;
&lt;p&gt;I'm still often being asked today: "But Dave, what does it mean, why do I have this warning? What can I do to resolve it?" These are all legitimate questions. To help you understand why it's important, I'll take an example using Entity Framework. Assuming we have the following DbContext &amp;amp; table:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleDbContext&lt;/span&gt; : &lt;span class="hljs-title"&gt;DbContext&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;virtual&lt;/span&gt; DbSet&amp;lt;User&amp;gt; Users { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }
}

&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;User&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;long&lt;/span&gt; Id { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; FullName { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; PhoneNumber { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the following service:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;interface&lt;/span&gt; &lt;span class="hljs-title"&gt;IExampleService&lt;/span&gt;
{
    IEnumerable&amp;lt;User&amp;gt;? GetUsers();
}

&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleService&lt;/span&gt; : &lt;span class="hljs-title"&gt;IExampleService&lt;/span&gt;
{
    &lt;span class="hljs-keyword"&gt;private&lt;/span&gt; &lt;span class="hljs-keyword"&gt;readonly&lt;/span&gt; ExampleDbContext _exampleDbContext;

    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-title"&gt;ExampleService&lt;/span&gt;(&lt;span class="hljs-params"&gt;ExampleDbContext exampleDbContext&lt;/span&gt;)&lt;/span&gt;
    {
        _exampleDbContext = exampleDbContext;
    }
    
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; IEnumerable&amp;lt;User&amp;gt;? GetUsers()
    {
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; _exampleDbContext.Users;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is an example I've already seen in the past which is a code smell:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-comment"&gt;// Assuming IExampleService is injected in the IoC container. &lt;/span&gt;
IExampleService exampleService = &lt;span class="hljs-literal"&gt;default&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; myUsers = exampleService.GetUsers();

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (myUsers.Any())
{
    &lt;span class="hljs-keyword"&gt;foreach&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; myUser &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; myUsers)
    {
        &lt;span class="hljs-comment"&gt;// do something per user.&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, under JetBrains Rider or ReSharper, you would see the following error underlining the variable "myUsers":&amp;nbsp;&lt;img src="https://www-preprod.davidhome.net/contentassets/af3d253cd7a042f5b07bf696809a4a37/screenshot_20230217_0320251.png" alt="Figure 1" width="607" height="316" /&gt;&lt;/p&gt;
&lt;p&gt;In our previous example, we're asking Entity Framework to do two database queries, one for letting us know how many users we have and the second one to give all users. The reason this part of the code is behaving like this is because of the way&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;reacts when being called. Contrary to what we expect it would do,&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;types are lazily loading what's inside when requested to do so. They are "Enumerating" their content one by one when only strictly requested. And when I say strictly when only requested, it's because that's kind of the case. See, for the following line of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; myUsers = exampleService.GetUsers();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will technically do "nothing". Try it under your debugger. You can put a breakpoint inside the&amp;nbsp;&lt;code&gt;ExampleService&lt;/code&gt;&amp;nbsp;and you will never break to this point getting past the variable assignation. You will though if you forcibly load the&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;in your debugger. When you ask the&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;to give you if there is "Any" objects inside it, it will execute the enumeration, thus the underlying concrete code responsible to load the content within it. Same as for the majority, if not all, Linq extensions (Where, Select, Sum, Aggregate, etc.). So, all in all, since we call "Any" and that we iterate over our enumerable in a foreach loop, we're enumerating our&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;twice, thus asking Entity Framework to give our users twice, causing two SQL queries. I think you can see where I'm going with that.&lt;/p&gt;
&lt;p&gt;In the past, I've resolved production performance issues by simply enumerating first with "ToArray" or "ToList". There was occasions where I've found bit of code doing&amp;nbsp;&lt;a href="https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping"&gt;N+1 requests&lt;/a&gt;&amp;nbsp;against the database.&lt;/p&gt;
&lt;p&gt;So, if we want to prevent that, with the previous example, we could have done something as the following to prevent multiple enumerations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-comment"&gt;// Assuming IExampleService is injected in the IoC container. &lt;/span&gt;
IExampleService exampleService = &lt;span class="hljs-literal"&gt;default&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; myUsers = exampleService.GetUsers();
&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; users = myUsers &lt;span class="hljs-keyword"&gt;as&lt;/span&gt; User[] ?? myUsers.ToArray();

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (users.Any())
{
    &lt;span class="hljs-keyword"&gt;foreach&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; myUser &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; users)
    {
        &lt;span class="hljs-comment"&gt;// do something per user.&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the variable&amp;nbsp;&lt;code&gt;users&lt;/code&gt;&amp;nbsp;is the result of an enumeration of my&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;, doing further manipulation of the array only plays with the in-memory values.&lt;/p&gt;
&lt;p&gt;Some are asking me if they should be using&amp;nbsp;&lt;code&gt;IEnumerable&lt;/code&gt;&amp;nbsp;at all. As much as I understand the concern, its usage still depends on what you do inside your code. It's not bad per se, it's just that you shouldn't be using it systematically for everything. See, for our previous example, there's two additional ways I see you could expose your users through the&amp;nbsp;&lt;code&gt;ExampleService&lt;/code&gt;. You could decide, design wise, to allow additional queries to be done against this API call, so in this case I would put an&amp;nbsp;&lt;code&gt;IQueryable&amp;lt;User&amp;gt;&lt;/code&gt;&amp;nbsp;return type. This explicitly tell your consumer,&amp;nbsp;&lt;em&gt;hey be aware that you need to conclude the query with a "ToArray" or something like that&lt;/em&gt;. Alternatively, you could simply return an array of&amp;nbsp;&lt;code&gt;User&lt;/code&gt;&amp;nbsp;or, if you want to be cool, a&amp;nbsp;&lt;code&gt;IReadOnlyCollection&amp;lt;User&amp;gt;&lt;/code&gt;. Using this method assumes that by consuming your API, you shouldn't have to do another enumeration afterwards, so the in-memory list of users is enough.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Fri, 17 Feb 2023 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/optimizely-dxp-application-insights-instrumentation-key-vs-connection-string/</guid><link>https://www-preprod.davidhome.net/blog/optimizely-dxp-application-insights-instrumentation-key-vs-connection-string/</link><category>Optimizely</category><title>Optimizely DXP &amp; Application Insights | Instrumentation Key VS Connection String</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;You might be aware of the news,&amp;nbsp;&lt;a href="https://github.com/azure-deprecation/dashboard/issues/212"&gt;Microsoft is deprecating usage of the Instrumentation key&amp;ndash;based data injestion&lt;/a&gt;. It has been almost one year since its announcement, but it made me thing about one thing while working with the solution of one of our clients: Optimizely DXP (Digital Experience Cloud).&lt;/p&gt;
&lt;p&gt;When a client is subscribing to DXP, they are entitled to have a fully managed infrastructure in the Cloud to host their application, including 3 different environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integration&lt;/li&gt;
&lt;li&gt;PreProduction&lt;/li&gt;
&lt;li&gt;Production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's important to know that, for each environment, a different database and most notably&amp;nbsp;&lt;strong&gt;Application Insights&lt;/strong&gt;&amp;nbsp;instance is used. This in fact the reason learning about the announcement of Microsoft made me think, but what if we're using the Connection String instead of the Instrumentation Key inside my solution?&lt;/p&gt;
&lt;p&gt;So, some of you are already aware that we're able to view the configuration of the App Service of Integration. That lead me to find that currently speaking, only the environment variable APPINSIGHTS_INSTRUMENTATIONKEY is being used to configure the link between your application and Application Insights. Assuming this is the same setting for all environments, I realized that I cannot have a connection string configured on my local development environment - And anyway,&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings#appinsights_instrumentationkey"&gt;Microsoft is pretty much straight forward about that&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don't use both APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. Use of APPLICATIONINSIGHTS_CONNECTION_STRING is recommended.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even though they recommend usage of a connection string, we cannot, because DXP environments are configured using the instrumentation key. So, long story short, don't use the connection string on your development machine, or at least, remove it using transformations during the deployment. If you keep using a connection string, you will have trouble with your telemetry data - Either everything will go inside your development App Insights, or your app might simply trace nothing anymore. I did not test my previous statement, but I'm confident this is what will happen.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Fri, 17 Feb 2023 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/optimizely-cms-commerce-net-core-migration-tips-tricks/</guid><link>https://www-preprod.davidhome.net/blog/optimizely-cms-commerce-net-core-migration-tips-tricks/</link><category>Optimizely</category><title>Optimizely CMS &amp; Commerce .NET Core migration - Tips &amp; Tricks</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;Last September, we had an extraordinary opportunity to share our findings and experience migrating our first Optimizely solution to .NET Core. This event occured at the&amp;nbsp;&lt;a href="https://www.eventbrite.com/e/optimizely-dev-meet-up-tickets-397187196597"&gt;Optimizely Dev Meetup in Montreal&lt;/a&gt;. I was presenting alongside my colleagues Pierre Vignon and&amp;nbsp;&lt;a href="https://eric.st-pierre.xyz/"&gt;Eric&lt;/a&gt;. So, as the title suggests, our goal was to give tips &amp;amp; tricks on the migration of an Optimizely solution to .NET Core. This effort obviously came with some challenges!&lt;/p&gt;
&lt;p&gt;Here is a vague overview of the steps the process involves:&lt;/p&gt;
&lt;ol start="0"&gt;
&lt;li&gt;Preparation&lt;/li&gt;
&lt;li&gt;Project conversion&lt;/li&gt;
&lt;li&gt;Project runtime stabilizations
&lt;ol&gt;
&lt;li&gt;First boot stabilizations&lt;/li&gt;
&lt;li&gt;Runtime stabilizations&lt;/li&gt;
&lt;li&gt;Deployment pipelines&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Regression fixes&lt;/li&gt;
&lt;li&gt;Quality assurance&lt;/li&gt;
&lt;li&gt;Go live&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;You will need to plan the time required to do the migration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Implies you will prepare your team and your client of preferably freezing other development efforts during the migration&lt;/li&gt;
&lt;li&gt;Prepare your people: You should have a consistent team of developers, not planned to switch between projects during the migration&lt;/li&gt;
&lt;li&gt;Limit the size of the team: You can't have that many people at the same time on the migration itself. Sure, you could give a development boost at the end, but you could also be a victim of some pitfalls related to this method, by example, duplicating the efforts; Having more than one developer working on the same thing without knowledgeably be aware of it.&lt;/li&gt;
&lt;li&gt;Migrating a Commerce site? Plan for the removal of Commerce Manager; It's no longer existing in .NET Core.&lt;/li&gt;
&lt;li&gt;Make an inventory of your project dependencies - You need to quickly find any dependencies requiring a replacement. Your dependency tree should be as simple as possible to minimize the complexity when migrating from&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference"&gt;packages.config to PackageReference&lt;/a&gt;. So, the elements to verify on this point are the following:
&lt;ol&gt;
&lt;li&gt;Project dependency hierarchy&lt;/li&gt;
&lt;li&gt;NuGet package dependencies&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Have yourself a recent database backup of production. Please make sure to make data sanitization first to avoid having personally identifiable information.
&lt;ol&gt;
&lt;li&gt;We did this mistake and fell into the trap. We had a very old development database that was way too different than production. This caused one of our database migrations to fail because of a schema difference when we have deployed thereafter on PreProduction&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lessons &amp;amp; learned: The most overlooked part of this section were the dependencies of our solution. We learned at the project runtime stabilization step that some modules/plugins/dependencies weren't compatible anymore under .NET, so we had to find alternatives. Some of which weren't available except by re-implementing them from scratch. This is by far one of the biggest bottlenecks of a migration. We had to put our own efforts to migration certain plugins, e.g., a&amp;nbsp;&lt;a href="https://github.com/jacobjones/DoubleJay.Epi.ConfigurableColorPicker/pull/2"&gt;color picker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Optimizely also switched of image processing plugin from .NET Fx to .NET. It's now referring ImageSharp. You will have to consider changing all transformation logics inside your solution to something else, preferably using&amp;nbsp;&lt;a href="https://sixlabors.com/products/imagesharp-web/"&gt;ImageSharp.Web&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Project conversion&lt;/h2&gt;
&lt;p&gt;You will have to convert all projects to the new&amp;nbsp;&lt;code&gt;csproj&lt;/code&gt;&amp;nbsp;format. This process can be simplified using the&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/porting/#net-upgrade-assistant"&gt;.NET Upgrade Assistant&lt;/a&gt;. Refer to the&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/upgrade-assistant"&gt;Optimizely documentation&lt;/a&gt;&amp;nbsp;for more information. They also have created&amp;nbsp;&lt;a href="https://github.com/episerver/upgrade-assistant-extensions"&gt;an extension&lt;/a&gt;&amp;nbsp;over the upgrade assistant which is going to help you during the migration.&lt;/p&gt;
&lt;p&gt;The goal of this step is to convert all projects of the solution as quickly as possible to the newest format. Keep in mind this operation can only be done by a single developer or by pair programming if necessary. You can't have two developers on such major changes, you will simply end up with Git conflicts while trying to merge. So, typically, a single developer per project is recommended on this phase. Centralize your efforts on the migration, but not on making sure the site is compiling, it's expected not to compile after the first time the upgrade assistance as been ran.&lt;/p&gt;
&lt;p&gt;As soon as all&amp;nbsp;&lt;code&gt;csproj&lt;/code&gt;&amp;nbsp;are converted, you can easily share the rest of the conversion with all developers. From that point, you will have a lot of compilation errors. Here's a simple representation of items you need to check per project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Simplify the referenced packages.
&lt;ol&gt;
&lt;li&gt;Only add those who are necessary, they rest should be including implicitly (&lt;a href="https://devblogs.microsoft.com/nuget/introducing-transitive-dependencies-in-visual-studio/"&gt;Transitive dependency&lt;/a&gt;). Up until recently, you had to manage this per project, but now, with the&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management"&gt;centralized package management&lt;/a&gt;, you can make sure the same package versions are used within the whole solution.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Replace .NET Fx specific libraries to their .NET variants. E.g., Remove Microsoft.AspNet.Mvc and make sure only Microsoft.AspNetCore.Mvc is installed, either implicitly, or explicitly if desired&lt;/li&gt;
&lt;li&gt;Clean stale code - Remove everything that you don't need, avoid unnecessary conversion work on something that isn't being used at all.
&lt;ol&gt;
&lt;li&gt;This effort should be taken up front, since the beginning of the creation of the project. Leaving stale code inside a solution is just a way to confuse other developers and make everyone loose time unnecessarily.&lt;/li&gt;
&lt;li&gt;If you want to keep a code snippet, you should save it inside a Wiki note instead for further reference. In my opinion, your code repository isn't the place to save code snippets hidden in comments.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Make all the required remaining adjustment to make the project compile (not the solution, just the project).&lt;/li&gt;
&lt;li&gt;Ensure all unit and integration tests passes as early as possible. This will help you quickly find any further regressions if applicable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Developers has the habit to comment some part of the code when testing any changes, they've made. It's sure is a usual development style some people use, but do not take this reflex to comment everything just to reach your goal to compile the project. This is totally counter intuitive and you might even end forgetting critical parts to be migrated later.&lt;/p&gt;
&lt;p&gt;As for database migrations, we followed&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/ef/efcore-and-ef6/porting/port-code"&gt;Microsoft's recommendations&lt;/a&gt;&amp;nbsp;and removed all past migrations. The only thing that left was the database context and the database models. Technically speaking, they are already deployed to production, so they are no longer necessary anyway. Adding more migration will simply create a difference with the existing schema and your modification.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Project runtime stabilizations&lt;/h2&gt;
&lt;h3&gt;First boot stabilizations&lt;/h3&gt;
&lt;p&gt;Once all projects are compiling, you will then probably face a lot of runtime exceptions, especially at the first boot. Here are the commonly found issues we faced during our migration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuration issues. Web.config/app.config is no longer a thing under .NET. You will have to migrate your configuration and you might face issues in relation to this.&lt;/li&gt;
&lt;li&gt;IoC issues. You will probably forget to register a service or two. This happens, but it's quite simple to resolve usually speaking.&lt;/li&gt;
&lt;li&gt;MVC issues. Due to the nature of the migration and especially the fact the core MVC library changed, you might face issues with your views, your customizations around it and surely refactor a lot of&amp;nbsp;&lt;a href="https://github.com/dotnet/aspnetcore/issues/5110"&gt;&lt;code&gt;@helper&lt;/code&gt;&lt;/a&gt;&amp;nbsp;usage&lt;/li&gt;
&lt;li&gt;Your app is hosted in DXP? Prepare yourself to fix certain environment related hurdles, because it will be running under Linux. E.g., adjusting file paths, properly respecting their case.&lt;/li&gt;
&lt;li&gt;Database migrations: You might face hiccups with them. To this point, it's best to test your migrations with the command line&amp;nbsp;&lt;code&gt;dotnet ef&lt;/code&gt;. You'll find all necessary resources to debug what's causing the hitches.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Runtime stabilizations&lt;/h3&gt;
&lt;p&gt;After the first successful boot, you will surely face other complications that will require your team's attention. Notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Broken CSS styling&lt;/li&gt;
&lt;li&gt;Broken JavaScript&lt;/li&gt;
&lt;li&gt;Broken pages, e.g., certain content type could be causing a crash, but only at runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep a good attention of your browser developer console, you'll probably find a couple of errors here and there, mainly responsible because the default JSON serializer is now using camel case instead of pascal case under .NET Fx.&lt;/p&gt;
&lt;p&gt;It's very important at this phase that all developers work conjointly to find as much bugs as possible. Stack them as much as you can into the backlog. It's expected there will be a lot, so at least you can track their progress and present them to your QA team once you will reach the quality assurance phase.&lt;/p&gt;
&lt;h3&gt;DevOps Automations&lt;/h3&gt;
&lt;p&gt;In parallel, another person can work on adjusting your DevOps automations. Typically, the biggest part that will require many efforts will be the application compilation itself. Since we're switching from .NET Fx to .NET, it's best to switch using the .NET CLI under your scripts. Another thing to keep in mind; If you're deploying in DXP, make sure every package name are unique, otherwise you will receive an error stipulating the package has already been uploaded. In .NET, Optimizely prevents duplicates. The advantage though is that you can reuse an existing package to re-make a deployment without the need to re-upload it first.&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use .NET CLI&lt;/li&gt;
&lt;li&gt;Do not get mixed up with old&amp;nbsp;&lt;code&gt;*.config&lt;/code&gt;&amp;nbsp;configuration and the newest&amp;nbsp;&lt;code&gt;appsettings.json&lt;/code&gt;&amp;nbsp;one. The old configuration isn't working anymore. If you prefer, you could simply delete these old files in your repository to prevent any confusions.
&lt;ul&gt;
&lt;li&gt;Setup your configuration transformations if necessary. You can customize them per environment by creating an additional file inside your solution, e.g.,&amp;nbsp;&lt;code&gt;appsettings.Integration.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DXP: Package your application using&amp;nbsp;&lt;code&gt;linux-x64&lt;/code&gt;&amp;nbsp;architecture&lt;/li&gt;
&lt;li&gt;DXP: Make sure your application is referring the mandatory&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/digital-experience-platform/v1.2.0-dxp-cloud-services/docs/deploying-an-existing-cms-site"&gt;CMS&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://docs.developers.optimizely.com/digital-experience-platform/v1.2.0-dxp-cloud-services/docs/deploying-an-existing-commerce-site"&gt;Commerce&lt;/a&gt;&amp;nbsp;Cloud packages.&lt;/li&gt;
&lt;li&gt;DXP: Package version must always be unique. If you intend to use an existing version, simply leverage the name of the package already uploaded to start a new deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Regression fixes&lt;/h2&gt;
&lt;p&gt;To this point, you've gathered an enormous quantity of bugs inside your backlog. You now want the full team to fix them all together. Obviously, you will want all majorly impacting bugs to be fixed first, especially the ones causing crashes, until you reach certain nebulous bugs where you can't even tell if they've been provoked by the migration or if it was already there to begin with.&lt;/p&gt;
&lt;p&gt;Couple of things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use each of you team members strength to tackle the bugs. This will fasten the correction and will ultimately give you more time on the most complicated ones.&lt;/li&gt;
&lt;li&gt;You will have to replace and/or backport any unsupported plugins, if necessary&lt;/li&gt;
&lt;li&gt;Find &amp;amp; fix any Backoffice related issues (under ~/Episerver/Cms). We had some troubles at the time, but I doubt it will be the case today.&lt;/li&gt;
&lt;li&gt;Since your DevOps automation should be already working to this point, our recommendation is to deploy as many iterations of the site as possible. This will allow your team to quickly find more bugs as other items are being fixed in parallel.&lt;/li&gt;
&lt;li&gt;By the end of this adventure, you will want to simulate production installations to prepare yourself for the Go-Live. Goal here is to make sure the current production database state is still handling the migrations and the switch to .NET transparently. If your application is hosted under DXP, you can easily leverage the available "Project Migration" tab under the Optimizely PaaS portal to restore as much time as you want your existing .NET Fx production environment to your .NET production one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Quality Assurance&lt;/h2&gt;
&lt;p&gt;You're near the end. Your team of developers has found and fix every possible bug, now it's turn for your QA team to double check the stability of the whole site. They should get back to the reported bugs inside the backlog and verify every one of them to make sure they're fixed. In any case, they can reopen them if any issue arises. Also, very important they make a full regression testing, independently of the number of bugs logged or not, they want to verify every part of the site, even the most unused sections.&lt;/p&gt;
&lt;p&gt;Your QA team will then decide together whether the stability of the site is worthy for production or not. In all scenarios, they will probably log more bugs inside the backlog for the developers to look for them afterwards. If they decide the site is ready to be released, a code freeze should be put in place to prevent any further change that could cause unwanted regressions. If in contrary they find the site too unstable for their taste, they should put the developers precisely on priority items that are blocking the release to production. Other nonessential and non critical bugs that can be fixed after, should be postponed.&lt;/p&gt;
&lt;p&gt;In the meantime, another of your team member should have already started communications with Optimizely's team to prepare for the Go-Live. A release plan should detail every expected step your team needs to take for it. You also want to coordinate your DNS switch to your newest .NET application instance.&lt;/p&gt;
&lt;h2&gt;Go-Live&lt;/h2&gt;
&lt;p&gt;You've tested over and over, and your QA team is satisfied with the stability of your site, you are now ready for Go-Live. Even thought you've made your best to test everything, you should still expect things can go wrong during the procedure. Fortunately, if in any ways something goes bad, you can still use the old production environment, which should still be intact.&lt;/p&gt;
&lt;p&gt;A general idea of the steps to take:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep old .NET Fx environments intact&lt;/li&gt;
&lt;li&gt;Put maintenance page on .NET Fx production environnement&lt;/li&gt;
&lt;li&gt;Deploy to new .NET production environment&lt;/li&gt;
&lt;li&gt;Verify stability and every key element, e.g., capable of taking an order&lt;/li&gt;
&lt;li&gt;Switch DNS to new .NET production environment&lt;/li&gt;
&lt;li&gt;Monitor traffic &amp;amp; logs in case you missed something very important and that you need to make a hotfix&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;All in all, I loved the experience, it was quite a challenge! The things all developers loved the most during this adventure was the speed the site took to load. It's so fast, it's extremely unrecognizable from the .NET Fx variant of the site. Optimizely also published&amp;nbsp;&lt;a href="https://world.optimizely.com/resources/net/"&gt;some metrics&lt;/a&gt; on the subject and I'd say they aren't far fetched at all.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Tue, 07 Feb 2023 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/how-i-ended-up-fixing-a-bug-using-an-ioc-trick/</guid><link>https://www-preprod.davidhome.net/blog/how-i-ended-up-fixing-a-bug-using-an-ioc-trick/</link><category>Optimizely</category><title>How I ended up fixing a bug using an IoC trick</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;We did a first .NET Core migration of Optimizely for one of our client last year. We were one of the early adopters - As far as I know, I think we were told we were like the forth or fifth migration to be ever initiated as part of this effort. Optimizely first released their first stable .NET Core release back on Q4 2021 if my memory is correct. In Q1-2 of 2022 we then started to migrate our client and we did it successfully. The thing though, at the time we did the migration, only .NET 5 was targeted for certain Optimizely libraries, so had to do this migration in .NET 5, even though it was about to be EOL by the beginning of May 2022.&lt;/p&gt;
&lt;p&gt;This lead us to migrate our client a second time when all libraries were targeting .NET 6 later on the same year. This is where we faced a major issue with some of the carts on the website. Not systematically all of them, but it was quite a deal breaker.&lt;/p&gt;
&lt;p&gt;Under this solution, we have a custom&amp;nbsp;&lt;code&gt;IPayment&lt;/code&gt;, which was being used to secure different payment data. For simplifying my explanations, we'll call this custom payment a&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;(ooohh super original). This&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;was rarely used, thus the reason the problem wasn't occurring all the time. I ended up finding the root cause by these simple reproduction steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new payment using the type&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;with a call on&amp;nbsp;&lt;code&gt;IOrderGroupFactory.CreatePayment(orderGroup, typeof(CustomPayment));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set the previously created payment inside the cart form payments of the user&lt;/li&gt;
&lt;li&gt;Call&amp;nbsp;&lt;code&gt;IOrderRepository.Save(cart);&lt;/code&gt;&amp;nbsp;with the user's cart now containing the&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thereafter, every following http request would provoke a crash. They were all caused by any call being made on&amp;nbsp;&lt;code&gt;OrderRepository.Load&amp;lt;ICart&amp;gt;([...])&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Upon further investigating the problem, I found out the source of the issue was coming from Optimizely's library, which leads me to find a behavioral change on how the payments are deserialized between version 14.2.0 and 14.6.0 of Episerver.Commerce.Core. When decompiling both versions of the class&amp;nbsp;&lt;code&gt;PaymentConverter&lt;/code&gt;, the method&amp;nbsp;&lt;code&gt;Create&lt;/code&gt;&amp;nbsp;does things quite differently:&lt;/p&gt;
&lt;p&gt;Version 14.2.0 and similar:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;    &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;private&lt;/span&gt; IPayment &lt;span class="hljs-title"&gt;Create&lt;/span&gt;(&lt;span class="hljs-params"&gt;JObject jObject, Type objectType&lt;/span&gt;)&lt;/span&gt;
    {
      &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; typeName = JToken.op_Explicit(jObject[&lt;span class="hljs-string"&gt;"ImplementationClass"&lt;/span&gt;]);
      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;.IsNullOrEmpty(typeName))
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(objectType);

      Type type = Type.GetType(typeName);
      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; type == (Type) &lt;span class="hljs-literal"&gt;null&lt;/span&gt; || &lt;span class="hljs-keyword"&gt;typeof&lt;/span&gt; (Payment).IsAssignableFrom(type) &amp;amp;&amp;amp; &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._featureSwitch.Service.IsSerializedCartsEnabled() ? &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(objectType) : &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(type);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Version 14.6.0:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;private&lt;/span&gt; IPayment &lt;span class="hljs-title"&gt;Create&lt;/span&gt;(&lt;span class="hljs-params"&gt;JObject jObject, Type objectType&lt;/span&gt;)&lt;/span&gt;
    {
      &lt;span class="hljs-built_in"&gt;string&lt;/span&gt; str = JToken.op_Explicit(jObject[&lt;span class="hljs-string"&gt;"ImplementationClass"&lt;/span&gt;]);
      &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;.IsNullOrEmpty(str))
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(objectType);

      Type type = TypeResolver.GetType(str);
      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; type == (Type) &lt;span class="hljs-literal"&gt;null&lt;/span&gt; ? &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(objectType) : &lt;span class="hljs-keyword"&gt;this&lt;/span&gt;._paymentFactory.Service(type);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explanation time! So under the working version, the variable&amp;nbsp;&lt;code&gt;type&lt;/code&gt;&amp;nbsp;is indeed assignable from a&amp;nbsp;&lt;code&gt;Payment&lt;/code&gt;&amp;nbsp;and the feature switch is enabled. Since this is the case, the condition is using the variable&amp;nbsp;&lt;code&gt;objectType&lt;/code&gt;, which at runtime, is assigned with the value&amp;nbsp;&lt;code&gt;EPiServer.Commerce.Order.Internal.SerializablePayment&lt;/code&gt;. The&amp;nbsp;&lt;code&gt;paymentFactory&lt;/code&gt;&amp;nbsp;is a wrapper around the IoC ServiceProvider, so basically it creates an empty instance of a&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;. A&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;&amp;nbsp;is registered elsewhere by Optimizely inside the IoC container, so this is why this is working.&lt;/p&gt;
&lt;p&gt;As for the non-working version, the issue is quite evident. At the return statement, the variable&amp;nbsp;&lt;code&gt;type&lt;/code&gt;&amp;nbsp;is never null, so it calls the second end of the condition, the&amp;nbsp;&lt;code&gt;paymentFactory&lt;/code&gt;&amp;nbsp;with the variable&amp;nbsp;&lt;code&gt;type&lt;/code&gt;&amp;nbsp;as parameter. As we have never registered our custom payment under the IoC container, this line will return null and will cause a crash elsewhere in Optimizely's code.&lt;/p&gt;
&lt;p&gt;At first, I thought I could simply add our&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;inside the IoC container, but that caused another issue in relation to the deserialization logic of Optimizely. The type must absolutely be compatible with a&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;. This, of course, isn't the case; our custom payment is only inheriting from&amp;nbsp;&lt;code&gt;Mediachase.Commerce.Orders.Payment&lt;/code&gt;. To counter this issue, I used a very obscure and a "&lt;em&gt;I wouldn't recommend it&lt;/em&gt;" approach to resolve the problem. Before moving with the solution though, let me first tell you about type forwarding: As you're probably already aware, there's the existence of extension methods allowing you to "Forward" certain types to others under the IoC container, it is known to be available under the Optimizely libraries. It's also a practice other third party IoC containers have implemented, e.g., Windsor, which also has this forward type concept. All in all, the idea is quite straightforward: You want to have a certain service, to be registered under multiple different types. Any of the registered types will resolve the same service instance, based on its configured lifetime. This is what gave me the idea to resolve the issue like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;&lt;span class="hljs-comment"&gt;// The following two lines are the equivalence of this call: services.Forward&amp;lt;SerializablePayment, CustomPayment&amp;gt;();&lt;/span&gt;
&lt;span class="hljs-comment"&gt;// Except that on the second line, I cast the instance as IPayment instead of a SerializablePayment.&lt;/span&gt;
&lt;span class="hljs-comment"&gt;// This is a hack, it will force Epi to use its in house object when deserializing a CustomPayment. &lt;/span&gt;
&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; serviceDescriptor = services.First((Func&amp;lt;ServiceDescriptor, &lt;span class="hljs-built_in"&gt;bool&lt;/span&gt;&amp;gt;)(s =&amp;gt; s.ServiceType == &lt;span class="hljs-keyword"&gt;typeof&lt;/span&gt;(SerializablePayment)));
&lt;span class="hljs-comment"&gt;// ReSharper disable once RedundantCast -&amp;gt; Keep it, important.&lt;/span&gt;
services.Add(&lt;span class="hljs-keyword"&gt;typeof&lt;/span&gt;(CustomPayment), s =&amp;gt; s.GetService&amp;lt;SerializablePayment&amp;gt;() &lt;span class="hljs-keyword"&gt;as&lt;/span&gt; IPayment, serviceDescriptor.Lifetime);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Super sketchy, but it works. Like mentioned inside the comments, the code is inspired from the "Forward" extension method of Optimizely. It does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gets the service descriptor of the already registered&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;&amp;nbsp;service.&lt;/li&gt;
&lt;li&gt;Adds a new service of type&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;which resolves to the existing&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;&amp;nbsp;already registered within the IoC container.
&lt;ul&gt;
&lt;li&gt;The trick is to cast the&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;&amp;nbsp;as&amp;nbsp;&lt;code&gt;IPayment&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since both&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;&amp;nbsp;inherits from&amp;nbsp;&lt;code&gt;IPayment&lt;/code&gt;, this resolves our deserialization issue. At runtime, the call onto&amp;nbsp;&lt;code&gt;_paymentFactory.Service(type);&lt;/code&gt;&amp;nbsp;will simply load a&amp;nbsp;&lt;code&gt;SerializablePayment&lt;/code&gt;, even though the value of the variable&amp;nbsp;&lt;code&gt;type&lt;/code&gt;&amp;nbsp;is&amp;nbsp;&lt;code&gt;CustomPayment&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Edit: The bug has been fixed:&amp;nbsp;&lt;a href="https://world.optimizely.com/support/bug-list/bug/COM-16500"&gt;https://world.optimizely.com/support/bug-list/bug/COM-16500&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Tue, 10 Jan 2023 05:00:00 Z</pubDate></item><item><guid isPermaLink="true">https://www-preprod.davidhome.net/blog/don-t-manually-change-the-tls-version-using-servicepointmanager-securityprotocol/</guid><link>https://www-preprod.davidhome.net/blog/don-t-manually-change-the-tls-version-using-servicepointmanager-securityprotocol/</link><category>Programming</category><title>Don't manually change the TLS version using ServicePointManager.SecurityProtocol</title><description>&lt;div&gt;&lt;div&gt;
&lt;div&gt;&lt;p&gt;Coming late to the game, but I've often saw, in the TLS 1.0 &amp;amp; 1.1 deprecation era, people disabling these protocols by simply doing the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs hljs language-csharp" data-highlighted="yes"&gt;System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure, it will work, but this line of code is far from ideal and is not friendly maintainability wise. Reasons around that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You might have to write this line of code in a couple of different areas of your code&lt;/li&gt;
&lt;li&gt;You will have to keep track of those areas for future updates (e.g., for the deprecation of TLS 1.2 by example)&lt;/li&gt;
&lt;li&gt;It's not guaranteed third party libraries will work with the instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Microsoft&amp;nbsp;&lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/network-programming/tls"&gt;recommends&lt;/a&gt;&amp;nbsp;to do otherwise and I'll admit it's quite simpler this way than the other around. All in all, what you need inside your app configuration is the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml hljs" data-highlighted="yes"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;appSettings&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;key&lt;/span&gt;=&lt;span class="hljs-string"&gt;"AppContext.SetSwitch:Switch.System.Net.DontEnableSchUseStrongCrypto"&lt;/span&gt; &lt;span class="hljs-attr"&gt;value&lt;/span&gt;=&lt;span class="hljs-string"&gt;"false"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
    &lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;add&lt;/span&gt; &lt;span class="hljs-attr"&gt;key&lt;/span&gt;=&lt;span class="hljs-string"&gt;"AppContext.SetSwitch:Switch.System.Net.DontEnableSystemDefaultTlsVersions"&lt;/span&gt; &lt;span class="hljs-attr"&gt;value&lt;/span&gt;=&lt;span class="hljs-string"&gt;"false"&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;span class="hljs-tag"&gt;&amp;lt;/&lt;span class="hljs-name"&gt;appSettings&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This instructs your app to use the system recommended cyphers and TLS versions when your app is doing secure communications. So, without changing a single line of code, you're asking .NET to follow the system recommendations, not his.&lt;/p&gt;
&lt;p&gt;Keep in mind, per the documentation of Microsoft, these settings will have an impact on certain .NET Fx targets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For&amp;nbsp;&lt;code&gt;DontEnableSchUseStrongCrypto&lt;/code&gt;, applications compiled using a target framework lower than 4.6 are impacted&lt;/li&gt;
&lt;li&gt;For&amp;nbsp;&lt;code&gt;DontEnableSystemDefaultTlsVersions&lt;/code&gt;, targets lower than .NET 4.7 are impacted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otherwise, by default, these configurations are disabled. This means they use the recommended TLS version and strong cryptos.&lt;/p&gt;
&lt;p&gt;Another thing to point out, certain third-party libraries, compiled using old .NET targets will also be impacted. Say, by example, you have added a NuGet package targeting only .NET 4.5, well these settings are also impacting your reference. If by any way it's using TLS, the library will also use the system recommended versions. By experience, this also applies even though your app is compiled using a recent .NET target (e.g., 4.7.2). So, if you're not specifying the flags and that you're referencing a third-party library compiled with an older target, you should also specify the flags, as the library otherwise will fallback in using the wrong TLS version and cyphers.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description><pubDate>Tue, 10 Jan 2023 05:00:00 Z</pubDate></item></channel></rss>