tag:blogger.com,1999:blog-46893057545223038192024-03-27T07:37:50.538+01:00Andy ButlandSenior Developer and head of DXP at Umbraco. Previously with Zone, building solutions primarily on .NET and using Umbraco, EPiServer and Sitecore CMS. This blog is used as a repository for various tips, tricks, issues and impressions drawn from the use of technology my work and interests. All words are my own.Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.comBlogger148125tag:blogger.com,1999:blog-4689305754522303819.post-1760769739203092232023-10-29T16:10:00.003+01:002023-10-29T16:10:31.015+01:00Adding and Deleting Criteria<p>This is one of a series of posts looking at migrating Umbraco packages to Umbraco 14 and the new backoffice. Other posts in this series:</p>
<ol>
<li><a href="/2023/09/umbraco-14-package-migration.html">Introduction</a></li>
<li><a href="/2023/09/umbraco-14-package-migration-installing.html">Upgrading to Umbraco 14 Preview</a></li>
<li><a href="/2023/09/umbraco-14-create-property-editor.html">Creating a Property Editor With Umbraco 14</a></li>
<li>Adding and Deleting Criteria</li>
</ol>
<p>As of <a href="/2023/09/umbraco-14-create-property-editor.html">last time I posted</a> in this series, I had a partially working property editor for Personalisation Groups. It could display the information stored against the property, and I'd solved a couple of challenges around retrieving external data and injecting scripts dynaically at runtime.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72vVv6w9-92mjYWgWMyjDfHTDWQ8EtjffJ2sYuTkTKvcZr20OWEpudpWSSU6trUomfVhlotxH25_wJwgqwcSbLuGLF3xVV01W1Kzfvb-cocyfc4TtunXCJXeL3vRuO16eynsZS7uCyOp0KQnk9xD7pDdiYXNfvAHKam_Mi7SYPWBFlohK8MtZPpu9fr0gvTQdVLSxlQ/s1600/group-definition-property-editor.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1050" data-original-width="2937" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72vVv6w9-92mjYWgWMyjDfHTDWQ8EtjffJ2sYuTkTKvcZr20OWEpudpWSSU6trUomfVhlotxH25_wJwgqwcSbLuGLF3xVV01W1Kzfvb-cocyfc4TtunXCJXeL3vRuO16eynsZS7uCyOp0KQnk9xD7pDdiYXNfvAHKam_Mi7SYPWBFlohK8MtZPpu9fr0gvTQdVLSxlQ/s1600/group-definition-property-editor.png"/></a></div>
<p>There's not been too much progress since, but I've picked up a few more things along the way that I'll include as a bit of a "grab-bag" in this post.</p>
<hr/>
<p>First step to make the data editable was wiring up some button clicks. In angularjs we are used to:</p>
<pre style="font-size: 16px">
<button type="button" ng-click="delete($index)">Delete</button>
</pre>
<p>With Lit, the syntax is:</p>
<pre style="font-size: 16px">
<button type="button" @click=${() => this._removeCriteria(index)}>Delete</button>
</pre>
<hr/>
<p>With Lit being more of a library than a framework like angularjs, we likely end up using more native JavaScript like <i>getElementById</i>. The encapsulated nature of the web components approach means we don't have to worry about our IDs clashing with something from core or another package loaded in the Umbraco backoffice. But we do need to remember to reference the element not via <code>document.getElementById</code>, but rather with <code>this.shadowRoot?.getElementById</code>. For example, to add a new criteria to a group, I find the value of the selected one to add via:</p>
<pre style="font-size: 16px">
const selectedCriteriaAlias = (<HTMLSelectElement>this.shadowRoot?.getElementById("availableCriteriaSelect")).value;
</pre>
<hr/>
<p>In order to progress further I did a bit of digging around in the CMS code, and found a property editor that's fairly similar to what I'm building - the multi-URL picker. This also displays a list of items, with each on editied in a modal. The core property editors are generally broken into two files. For example the mult URL picker has a <a href="https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/709b3cbff988a3e4e30985578a4c140cd5b8b08b/src/packages/core/property-editor/uis/multi-url-picker/property-editor-ui-multi-url-picker.element.ts">property editor element</a> that in turn imports and uses an <a href="https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/65c14774c199bd69babd67413f41c63d88858eef/src/packages/core/components/input-multi-url/input-multi-url.element.ts">input element</a>.</p>
<p>I decided to break my editor up in a similar way, which was mostly straightforward but I did have a few issues that took me some time and help to resolve.</p>
<p>One was the incoming state to the input element being immutable, so if I wanted to add a new criteria to the array, I'd get an error like <code>Cannot add property 1, object is not extensible</code>. This was resolved by <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/66466cfcc99b45c7d23510342b831868237ef1a6/PersonalisationGroups/client-src/personalisation-groups/src/input-personalisation-group-definition.element.ts#L18">taking a copy of the property data</a> when it's first loaded into the input element.</p>
<p>Another was that when updates were made, they weren't being reflected in the UI. At this points I found I had to make <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/66466cfcc99b45c7d23510342b831868237ef1a6/PersonalisationGroups/client-src/personalisation-groups/src/input-personalisation-group-definition.element.ts#L104">explict calls to Lit's <code>this.requestUpdate()</code> method</a>.</p>
<p>The final one was simply getting anything other than my entry-point JavaScript file being included in the built output. That was fixed with exports, making sure the <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/66466cfcc99b45c7d23510342b831868237ef1a6/PersonalisationGroups/client-src/personalisation-groups/src/property-editor-ui-personalisation-group-definition.element.ts#L43">input element is also exported from the property editor element</a>.</p>
<hr/>
<p>My last, as yet incomplete, effort for now was attempting to adapt the code in the multi-URL picker to open a modal for editing the definition of the group. There I ran into an issue that a key base class used by the core CMS modals - <code>UmbModalBaseElement</code> - is <a href="https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/40e04294f5924558d7c1a33e5f5416d5815a90c7/src/packages/core/modal/common/link-picker/link-picker-modal.element.ts#L10">marked as internal</a>. Fortunately though I can see that's the case of of the latest preview release (preview 3, at the time of writing), but it has changed when I look at the latest code in the GitHub repo. So this will be one to wait for preview 4 for, and give it another go.</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com2tag:blogger.com,1999:blog-4689305754522303819.post-9296130259839919492023-09-23T22:03:00.013+02:002023-10-29T16:12:08.538+01:00Creating a Property Editor With Umbraco 14<p>This is one of a series of posts looking at migrating Umbraco packages to Umbraco 14 and the new backoffice. Other posts in this series:</p>
<ol>
<li><a href="/2023/09/umbraco-14-package-migration.html">Introduction</a></li>
<li><a href="/2023/09/umbraco-14-package-migration-installing.html">Upgrading to Umbraco 14 Preview</a></li>
<li>Creating a Property Editor With Umbraco 14</li>
<li><a href="/2023/10/adding-and-deleting-criteria.html">Adding and Deleting Criteria</a></li>
</ol>
<h2>A "Hello World" Property Editor</h2>
<p>The Umbraco documentation is a good guide for getting started, with details on <a href="https://docs.umbraco.com/umbraco-backoffice/tutorials/creating-your-first-extension">creating an extension</a> and <a href="https://docs.umbraco.com/umbraco-backoffice/tutorials/creating-a-property-editor">creating a property editor</a>.</p>
<p>Using the scripts provided I created an extension within a folder created in my main package project that I called <i>client-src</i>.</p>
<pre style="font-size: 16px">
npm create vite@latest -- --template lit-ts personalisation-groups
cd personalisation-groups
npm install
npm install -D @umbraco-cms/backoffice@next
</pre>
<p>This sets up some files that make up an extension to the backoffice, using the latest preview release from the Umbraco prereleases feed.</p>
<p>Next step as described in the docs is to add a <i>vite.config.ts</i> file. <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/client-src/personalisation-groups/vite.config.ts">I set mine up</a> to export the built files rather than to the the default dist folder, to the <i>/App_Plugins/PersonalisationGroups</i> folder, which is where they will need to be for package distribution.</p>
<p>I then modifed the property editor element file to just display a message in the backoffice.</p>
<p>Finally, <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/App_Plugins/PersonalisationGroups/umbraco-package.json">I added an <i>umbraco-package.json</i> file</a> that registers the extension with the backoffice. The important parameters to set correctly are <i>js</i>, which needs to reference the compiled JavaScript entry point, and <i>propertyEditorSchemaAlias</i>, which needs to match the alias defined by the server-side property editor. This server-side component <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups.Core/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs">is much as it was in Umbraco 12</a>, but there are some properties such as the "view" and "icon", that are now picked up from the client-side registration.</p>
<p>Having manually copied over the contents of the <i>/App_Plugins/PersonalisationGroups</i> to the equivalent location under the Umbraco 14 test site, I could restart it and view the results. Under <i>Settings > Data Types</i>, I could update my data type to reference the property editor that was now available in the dialog.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD5Ej4nUqahhvZagDOfSkQ3j2QmsD-qO0OFJw8FqHQEejOEnVOUJU29T696Pp9GOBwA1M7xyNRcCosWbmkS4MrID_jBOpjjv6Lc2hD9_SoG-oxbAJLgAEHRscvD7Ls96K3ZsWXkfP1BopbXQ_ZACBVo2v0DE44CKL8wOB4gjhEWnunfaz79QiN_Qz2NN7qwmBCFu0O1g/s1600/blog-select-property-editor-ui.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1195" data-original-width="3046" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD5Ej4nUqahhvZagDOfSkQ3j2QmsD-qO0OFJw8FqHQEejOEnVOUJU29T696Pp9GOBwA1M7xyNRcCosWbmkS4MrID_jBOpjjv6Lc2hD9_SoG-oxbAJLgAEHRscvD7Ls96K3ZsWXkfP1BopbXQ_ZACBVo2v0DE44CKL8wOB4gjhEWnunfaz79QiN_Qz2NN7qwmBCFu0O1g/s1600/blog-select-property-editor-ui.png"/></a></div>
<p>And viewing the content, the "hello world" message was rendered.</p>
<h2>Migrating the Property Editor</h2>
<p>To go beyond the most basic example it was time to start migrating over the functionality and presentation from the angularjs version of the property editor. Here you basically combine two files - the angularjs <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/473f86e216884c6a5fe2814560cf1b4bd258cac5/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-group-definition.html">view</a> and <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/473f86e216884c6a5fe2814560cf1b4bd258cac5/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js">controller</a> - into one, <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/client-src/personalisation-groups/src/property-editor-ui-group-definition.element.ts">the implementation of UmbPropertyEditorExtensionElement</a>. You can see my before and (work in progress) after if you follow the links in the previous sentence.</p>
<p>For the view, this can be copied into the <i>render()</i> function. Angularjs should can be removed. Instead, template string interpolation can be used to render properties from the class.</p>
<p>Functions from the controller can be moved into the element class, making use of Typescript enhancements to mark them with appropriate access modifiers (generally <i>private</i>, and following the convention of an underscore prefix). I'm still getting used to the static typing, but as a C# developer it's certainly welcome. Rather than working with raw JSON as was necessary previously with the property editor value, it's now possible to <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/client-src/personalisation-groups/src/types.ts">define and import types</a>, and have compile time checks for correct usage when passing into functions.</p>
<h3>Accessing External Data</h3>
<p>Oftentimes a property editor needs to retrieve some data from Umbraco. Personalisation Groups needs to retrieve a list of criteria that are registered with the package, which is obtained via a <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/feature/migrate-to-14/PersonalisationGroups.Core/Controllers/CriteriaController.cs">server-side controller</a>.</p>
<p>For Umbraco 12, I inject the <i>$http</i> angularjs service and <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/473f86e216884c6a5fe2814560cf1b4bd258cac5/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js#L20">use that to retrieve the data</a>.</p>
<p>For Umbraco 14, we can <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/client-src/personalisation-groups/src/property-editor-ui-group-definition.element.ts#L21">use the native <i>fetch</i> method</a>.</p>
<h3>Replacing Angularjs Injectors with Dynamic Imports</h3>
<p>In order that the personalisation package could be extensible, I have a feature where criteria can be registered and loaded at runtime. Part of the criteria implementation is a JavaScript function responsible for translating the raw data stored with the property editor to something more friendly to display to editors. For example, the "day of week" criteria translates an array of day numbers, e.g. "1, 3, 5", to a display value of "Monday, Wednesday, Friday".</p>
<p>In the Umbraco 12 implementation, this is a small <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/473f86e216884c6a5fe2814560cf1b4bd258cac5/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js">angularjs service</a>, which is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/473f86e216884c6a5fe2814560cf1b4bd258cac5/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js#L12">pulled in at runtime using the <i>$injector</i> service</a>.</p>
<p>With Umbraco 14, this becomes a <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js">vanilla JavaScript module</a>, accessed using Typescript <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/c27a82df9204a8198cd86e51cc44e74747813de7/PersonalisationGroups/client-src/personalisation-groups/src/property-editor-ui-group-definition.element.ts#L29">dynamic import statements</a>.</p>
<p>Following a fair bit of trial and error my state of play is a partially functioning property editor, displaying the information held in the property data.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72vVv6w9-92mjYWgWMyjDfHTDWQ8EtjffJ2sYuTkTKvcZr20OWEpudpWSSU6trUomfVhlotxH25_wJwgqwcSbLuGLF3xVV01W1Kzfvb-cocyfc4TtunXCJXeL3vRuO16eynsZS7uCyOp0KQnk9xD7pDdiYXNfvAHKam_Mi7SYPWBFlohK8MtZPpu9fr0gvTQdVLSxlQ/s1600/group-definition-property-editor.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1050" data-original-width="2937" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg72vVv6w9-92mjYWgWMyjDfHTDWQ8EtjffJ2sYuTkTKvcZr20OWEpudpWSSU6trUomfVhlotxH25_wJwgqwcSbLuGLF3xVV01W1Kzfvb-cocyfc4TtunXCJXeL3vRuO16eynsZS7uCyOp0KQnk9xD7pDdiYXNfvAHKam_Mi7SYPWBFlohK8MtZPpu9fr0gvTQdVLSxlQ/s1600/group-definition-property-editor.png"/></a></div>
<p>Next steps are to improve styling, using some of the <a href="https://docs.umbraco.com/umbraco-cms/extending/ui-library">Umbraco UI library elements</a>. And also move on to the editing side of things - allowing new criteria to be added and existing ones edited via a right-hand panel dialog.</p>
<p>For more complicated packages, there's probably value in more structure than I have so far too. If you look at the core property editors, they all use nested components (see the slider property editor <a href="https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/709b3cbff988a3e4e30985578a4c140cd5b8b08b/src/packages/core/property-editor/uis/slider/property-editor-ui-slider.element.ts">here</a> for example). It may also be useful to introduce a service or resource class to wrap the data access responsibilities. It's probably not needed here, but it'll be something to look into for future work on larger projects.</p>
<p>To be continued... sometime...</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com1tag:blogger.com,1999:blog-4689305754522303819.post-31987729296146692322023-09-23T22:03:00.012+02:002023-10-29T16:11:55.807+01:00Upgrading to Umbraco 14 Preview<p>This is one of a series of posts looking at migrating Umbraco packages to Umbraco 14 and the new backoffice. Other posts in this series:</p>
<ol>
<li><a href="/2023/09/umbraco-14-package-migration.html">Introduction</a></li>
<li>Upgrading to Umbraco 14 Preview</li>
<li><a href="/2023/09/umbraco-14-create-property-editor.html">Creating a Property Editor With Umbraco 14</a></li>
<li><a href="/2023/10/adding-and-deleting-criteria.html">Adding and Deleting Criteria</a></li>
</ol>
<h2>Prerequisites</h2>
<p>As indicated in the <a href="https://docs.umbraco.com/umbraco-backoffice/extending/development-flow">official documentation</a>, to work with Umbraco 14 it's likely your development machine needs some updates, which was the case for me. You'll need:</p>
<ul>
<li>To be running .NET 8, which is <a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0">available at the time of writing in a first release candidate</a>.</li>
<li>To be on at least the v17.8 latest preview version of Visual Studio. To download and install this you may need to <a href="https://www.c-sharpcorner.com/article/how-to-enable-preview-version-in-visual-studio-2022/">change the channel used by the installer</a>.</li>
<li>To install a minimum version of Node.js 18.16. I have <a href="https://github.com/coreybutler/nvm-windows">Node Version Manager for Windows</a> installed, which is handy tool both for installing new versions and also switching between versions that you may need for different projects.</li>
</ul>
<h2>Package Update</h2>
<p>The package I'm working on consists of a solution containing couple of projects, with most C# code in a "core" project which is depended on by the installable package project that holds the front-end code. There's a test project and several example Umbraco sites, for 9, 10, 11 and 12. I've been fortunate in that I haven't run into any breaking changes migrating from 9 to 12, and as such there's only one version of the package, that depends on Umbraco 9, but works across 10, 11 and 12 too.</p>
<p>In a new branch I've removed the test sites and then made the following updates to migrate to Umbraco 14.</p>
<p>Firstly, added a NuGet.config file in the root of the solution such that I can reference the <a href="https://docs.umbraco.com/umbraco-backoffice/getting-started/installing-preview-builds">Umbraco prerelease feed</a>.</p>
<p>In the .csproj files I updated the <i>TargetFramework</i> setting from <i>net5.0</i> to <i>net8.0</i>.</p>
<p>And I updated the CMS dependency version from to <i>[9.0.0, 13)</i> to <i>14.0.0--preview003</i>.</p>
<p>Crossed my fingers and rebuilt... and it compiles! So no breaking changes moving up to Umbraco 14 either. Well, none in the C# code anyway...</p>
<h2>Creating an Umbraco 14 Test Site</h2>
<p>I then wanted to add back a test site for Umbraco 14. To do this I ran the following:</p>
<pre style="font-size: 16px">
dotnet new install Umbraco.Templates::14.0.0--preview003
dotnet new umbraco -n TestWebApp.V14
</pre>
<p>Having added the resulting project to the solution I then took a project dependency my package, and tried to build again. This time I hit an issue - an error of <i>The fully qualified file name must be less than 260 characters.</i> Specifically: </p>
<pre style="font-size: 13px">
C:\Users\abutl\.nuget\packages\umbraco.cms.staticassets\14.0.0--preview002\staticwebassets\umbraco\backoffice\packages\core\property-editor\uis\collection-view\config\bulk-action-permissions\property-editor-ui-collection-view-bulk-action-permissions.element.d.ts exceeds the OS max path limit.
The fully qualified file name must be less than 260 characters.
</pre>
<p>Fortunately this is a Windows limitation that is <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=powershell#enable-long-paths-in-w[…]-version-1607-and-later">fairly easily fixed</a> by tweaking a registry setting to enable long path support. You can do so by running this Powershell:</p>
<pre style="font-size: 16px">
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
</pre>
<p>After a Visual Studio restart, the build now succeeds.</p>
<h2>Migrating from an Umbraco 12 Database</h2>
<p>The next step I tried was to run the Umbraco 14 site, whilst connected to the database from my test site for Umbraco 12. Accessing the backoffice to start the migration leads to an issue though: <i>Invalid object name 'umbracoOpenIddictApplications'</i>.</p>
<p>It looks like there's a catch-22 here - to login and run the migration we need some tables to exist, but these tables need to exist to login. I think this wouldn't be an issue if I had migrated through Umbraco 13, but in any case have <a href="https://github.com/umbraco/Umbraco-CMS/issues/14860">raised this as an issue on the tracker</a>.</p>
<p>There's a workaround - just use the <a href="https://docs.umbraco.com/umbraco-cms/reference/configuration/unattendedsettings">unattended install options</a>.</p>
<p>This works to run the migrations but there looks to be an issue with one of them:</p>
<pre style="font-size: 13px">
BootFailedException: Boot failed: Umbraco cannot run. See Umbraco's log file for more details.
Umbraco.Cms.Core.Exceptions.BootFailedException: An error occurred while running the unattended upgrade.
The database configuration failed with the following message: The given key 'ID' was not present in the dictionary.
</pre>
<p>Tracing this through indicates something not working correctly with one of the migration steps named <i>AddGuidsToUserGroups</i>. Again I've <a href="https://github.com/umbraco/Umbraco-CMS/issues/14861">raised this on the tracker</a> so hopefully it'll be quickly resolved, but in the meantime I got around this by running a SQL script that does what the migration attempts to, which I'll share below:</p>
<pre style="font-size: 16px">
ALTER TABLE umbracoUserGroup
ADD [key] uniqueidentifier NOT NULL DEFAULT NEWID()
GO
CREATE NONCLUSTERED INDEX IX_umbracoUserGroup_userGroupKey ON dbo.umbracoUserGroup (
[key]
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
UPDATE umbracoUserGroup SET [key] = 'E5E7F6C8-7F9C-4B5B-8D5D-9E1E5A4F7E4D' WHERE userGroupAlias = 'admin'
UPDATE umbracoUserGroup SET [key] = '9FC2A16F-528C-46D6-A014-75BF4EC2480C' WHERE userGroupAlias = 'writer'
UPDATE umbracoUserGroup SET [key] = '44DC260E-B4D4-4DD9-9081-EEC5598F1641' WHERE userGroupAlias = 'editor'
UPDATE umbracoUserGroup SET [key] = 'F2012E4C-D232-4BD1-8EAE-4384032D97D8' WHERE userGroupAlias = 'translator'
UPDATE umbracoUserGroup SET [key] = '8C6AD70F-D307-4E4A-AF58-72C2E4E9439D' WHERE userGroupAlias = 'sensitiveData'
GO
</pre>
<p>With this database update, the migration step is skipped over, allowing the migration plan as a whole to run to completion.</p>
<p>And with that, we have a backoffice!</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS_k6OHPxbHsG0VCC8_f-OSm3YZS2C3dWLDQKDskMVpkMpD1f133s45G04PgLxL7NuyPwyDKAklD1QIwNE0yC9W2zZWX_0eQQ6xhahwSMkcvQ--Nx2oVBonMEJZr8nU2vwWoIj2q3eBCkTFm0tDcfJIvJ-4Op1nLKqMThl4EGkWs13KiScneYBS-q3OY2lTWuBF9Fzqg/s1600/blog-new-backoffice.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="397" data-original-width="1067" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS_k6OHPxbHsG0VCC8_f-OSm3YZS2C3dWLDQKDskMVpkMpD1f133s45G04PgLxL7NuyPwyDKAklD1QIwNE0yC9W2zZWX_0eQQ6xhahwSMkcvQ--Nx2oVBonMEJZr8nU2vwWoIj2q3eBCkTFm0tDcfJIvJ-4Op1nLKqMThl4EGkWs13KiScneYBS-q3OY2lTWuBF9Fzqg/s1600/blog-new-backoffice.png"/></a></div>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-84380384488628711982023-09-23T22:03:00.011+02:002023-10-29T16:11:40.027+01:00First Steps with Umbraco 14<p>Probably the largest change coming in the Umbraco ecosystem is the <a href="https://umbraco.com/blog/bellissima-preview-releases-of-the-new-backoffice/">development of a new backoffice</a>, removing the dependency on the legacy angularjs and moving to a modern front-end stack based on web components and Typescript. Not just for HQ in the work needed to update the CMS itself and associated commercial products. But also for the community, whether that be package developers or custom solution implementors looking to extend the backoffice.</p>
<p>It reminds me a little of the time coming up to Umbraco 9, the first version running on modern .NET. There was a lot of collbarative effort and information sharing then, as many people were learning new technology and ways of doing things. I'm hoping the next nine months or so leading up to Umbraco 14 will be a similar experience.</p>
<p>Although I'm now working at HQ, I'm in a similar position to many in finding a lot of the change new and something I'm going to need to get up to speed with. Like, I suspect, a number of Umbraco developers, I'm more comfortable on the back-end. And whilst over the years I managed have picked up enough front-end to be at least somewhat useful; particularly when it comes to modern JavaScript, I'm a little behind the times.</p>
<p>Leading up to the .NET Core release, I put together a <a href="https://www.andybutland.dev/2021/03/umbraco-package-migration-to-net-core.html">series of blog posts</a> describing how I went about converting a small package to Umbraco 9. It was very much learning in public, with lots of false starts and things that got improved along the way.</p>
<p>This week, with the pre-release of Umbraco 14 preview 3, I thought it was time to start digging into it. And so again, I decided to blog as I go.</p>
<p>Please bear in mind that this isn't official documentation - which is <a href="https://docs.umbraco.com/umbraco-backoffice">coming along nicely</a> - and at the various festivals, meet-ups and conferences coming up, you'll see and hear from many more more qualified people to advise on how to approach front-end package development and best practices with extending the new backoffice. But, as I say, I'm expecting there are many others in a similar place - new to the technology, not quite sure where to start and how to progress, and so hopefully there'll be some value in sharing as I go.</p>
<p>As last time, I'll work on a small package I keep updated with new Umbraco versions called <a href="https://marketplace.umbraco.com/package/umbracopersonalisationgroups">Personalisation Groups</a>. UI-wise it's not massive, but does have a reasonably involved property editor, so there should be enough to reflect something that's real-world, but not too complex in terms of it's specific functionality.</p>
<p>I'll keep this index page updated with links to the subsequent articles written after working on various bits, and you can find the source code <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, all work is in the <b>feature/migrate-to-14</b> branch.</p>
<ol>
<li>Introduction</li>
<li><a href="/2023/09/umbraco-14-package-migration-installing.html">Upgrading to Umbraco 14 Preview</a></li>
<li><a href="/2023/09/umbraco-14-create-property-editor.html">Creating a Property Editor With Umbraco 14</a></li>
<li><a href="/2023/10/adding-and-deleting-criteria.html">Adding and Deleting Criteria</a></li>
</ol>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-74840811355521541332023-07-30T15:42:00.000+02:002023-07-30T15:42:00.273+02:00Migrating An API from Newtonsoft.Json to System.Text.Json<p>In some recent work with Umbraco I’ve been looking to migrate an API from using the <a href="https://www.newtonsoft.com/json">Newtonsoft.Json</a> serialization library to the newer, Microsoft one, <a href="https://learn.microsoft.com/en-us/dotnet/api/system.text.json?view=net-7.0">System.Text.Json</a>.</p>
<p>The reason for doing this was to align the API we created for <a href="https://umbraco.com/products/umbraco-forms/">Umbraco Forms</a> with the <a href="https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api">content delivery API</a> available in Umbraco 12. We’re keen to ensure these APIs across Umbraco products are similar both in terms of behaviour but also behind the scenes when it comes to the libraries we are using.</p>
<p>As such I had a few updates to make – mostly straightforward, and some that needed a bit more research and development.</p>
<h2>Deserialization via a Model Binder</h2>
<p>When a form is submitted, a model binder is referenced in the controller’s action method to deserialize the incoming request to a strongly typed model:</p>
<script src="https://gist.github.com/AndyButland/50421fbe93a5465fac0c8d312f915c28.js"></script>
<p>Within the model binder we had the following code to deserialize the request into the appropriate type:</p>
<script src="https://gist.github.com/AndyButland/fd5c7ec8b07d44c7b09d4b65abd65858.js"></script>
<p>To convert this it was just necessary to change <i>JsonConvert.DeserializeObject</i> to <i>JsonSerializer.Deserialize</i>, and replace the <i>Newtonsoft.Json</i> using statement with <i>System.Text.Json</i>.</p>
<script src="https://gist.github.com/AndyButland/4e39ad907b0e4bf13c1e1d6907c5b1ea.js"></script>
<h2>Defining Serialization Behaviour</h2>
<p>In order to manage serialization settings such as null handling and property ordering, we make use of an attribute that decorates the API controllers at the class level.</p>
<p>The attribute applies settings for serializing the result objects and collections when a 200 “OK” response is returned:</p>
<ul>
<li>Any properties that have null values are omitted from the response.</li>
<li>Dictionary keys, as well as all other properties, are camel-cased.</li>
<li>Enums are returned as strings.</li>
<li>The properties are returned in alphabetical order.</li>
</ul>
<p>The Newstonsoft implementation requires a custom <i>DefaultContractResolver</i>, and looks like this:</p>
<script src="https://gist.github.com/AndyButland/b76b5a782ddb8df2813b68d63ea4d408.js"></script>
<p>To do the same thing with <i>System.Text.Json</i> was similar when it came to defining camel casing and null handling, but required a new feature available only in .NET 7 to be able to replicate the property ordering. Hat-tip to <a href="https://stackoverflow.com/users/3744182/dbc">dbc</a> on Stack Overflow for <a href="https://stackoverflow.com/a/72593993">this answer</a>.</p>
<script src="https://gist.github.com/AndyButland/d0dc71ba2312303c95624fdff8db74d5.js"></script>
<h2>Creating a JsonConverter</h2>
<p>Another fiddly migration was in implementing a <a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-7-0">JsonConverter</a>. These are classes that a responsible for converting an object to and from JSON. For primitive types and collections, you don’t need to do anything special as these conversions are automatic. But you may have a use case where you need to do something more than the default behaviour of the library provides.</p>
<p>The example we had was for handling a form submission where each value could either be a string, or a collection of strings. A “name” field for example would accept a single string input such as “Fred”, and a “favourite colours” multiple choice field could receive a collection, such as “Red”, “Green”.</p>
<script src="https://gist.github.com/AndyButland/ac4ef52b6f26074d0853b1233e436090.js"></script>
<p>We defined the <i>Values</i> property as a collection so needed the converter to populate it either with the collection posted (for multiple choice fields) or as a collection with a single element (for single value input fields).</p>
<p>Both libraries require decorating the property with an attribute indicating the converter to use. The only difference was the namespace needed for the using statement (<i>Newtonsoft.Json</i> or <i>System.Text.Json.Serialization</i>).</p>
<script src="https://gist.github.com/AndyButland/8f3d87c17c14b89375bb610688d7be34.js"></script>
<p>For the NewtonSoft implementation, it was necessary to inherit from the base <i>JsonConverter</i> class, and override the <i>ReadJson</i> method. Within that, we can examine the incoming raw JSON, determine if we have an array or a single value, and act according to set the values on the property.</p>
<p>The full class looked like this:</p>
<script src="https://gist.github.com/AndyButland/03108ff4f7a968bf6993084d85b5a268.js"></script>
<p>For <i>System.Text.Json</i>, there is a similar base class, but this time we have to provide type parameters. We get access to the raw JSON but this time as a stream that we examine and act on. By checking the token type at expected points, we can check if we have a single string value or the start of an array, and again process as appropriate.</p>
<script src="https://gist.github.com/AndyButland/c4e47c78e403d4b8805bc7afc299e279.js"></script>
<hr/>
<p>Since it's release in .NET Core 3.1, migrating older projects to the newer <i>System.Text.Json</i> serialization library may have been, and may continue to be, common task for developers. It's fine to stick to the tried and tested, but to benefit from the improved performance, security, and standards compliance with the new library, it'll be necessary to move forward and use it. This article showed a few serialization features we were using and how we migrated to the newer library. There's likely many more uses that people will run into too... for which <a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-7-0">information is available at the Microsoft docs</a>.</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-49755668290690570852023-06-12T22:24:00.006+02:002023-06-12T22:24:54.742+02:00Introducing Redis and Azure Cognitive Search to Improve API Performance<p>Cross-posting a couple of articles written for the Umbraco blog on the introduction of Redis Distributed Cache and Azure Cognitive Search to the <a href="https://marketplace.umbraco.com/">Umbraco Marketplace</a>, in order to improve performance and functionality:</p>
<ul>
<li><a href="https://umbraco.com/blog/introducing-redis-and-azure-cognitive-search-to-the-umbraco-marketplace/">Introducing Redis Distributed Cache to the Umbraco Marketplace</a></li>
<li><a href="https://umbraco.com/blog/populating-and-querying-azure-cognitive-search-in-the-umbraco-marketplace/">Populating and Querying Azure Cognitive Search in the Umbraco Marketplace</a></li>
</ul>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-17235265027214292092023-04-12T14:13:00.000+02:002023-04-12T14:13:09.625+02:00Programmatically Creating a Record With Umbraco Forms<p><a href="https://umbraco.com/products/umbraco-forms/">Umbraco Forms</a> is a commercial add-on for Umbraco providing an editor interface for creating and managing forms. These forms can then be hosted on the website for completion by site visitors, with the entries available for view in the Umbraco backoffice.</p>
<p>That describes the usual use-case, but it is also possible to create form submissions - known as records - for a form programatically.</p>
<p>Posting for future reference, this illustrative example for Forms 10 shows how this is done:</p>
<script src="https://gist.github.com/AndyButland/3463cc414f405fdbef556465017dc456.js"></script>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-51089561604562741922023-02-10T15:25:00.005+01:002023-03-07T09:03:08.638+01:00Use PostConfigure For Default Configuration of Collections in ASP.NET<p>This post contains a couple of topics that came together in a recent update I was making - one a gotcha, and one a solution, that as well as addressing this issue has wider application.</p>
<p>In my web application there was some configuration that consisted of a list of elements. This was represented in code with a POCO like this:</p>
<script src="https://gist.github.com/AndyButland/aa88d634cffe71eefacd088ab8fae28a.js"></script>
<p>Which can then be injected using <i>IOptions<MySettings></i> into the classes that need it.</p>
<p>And in configuration, via the <i>appSettings.json</i> file, you can provide the values:</p>
<script src="https://gist.github.com/AndyButland/786238dde97c965ebdee3142b7a39573.js"></script>
<p>So far so good, but now I wanted to set some default values for this configuration. If nothing is provided, I wanted a non-empty list to be used as the default. But if the user has configured something for the setting, then that should be used.</p>
<p>I figured I could just do the following, setting an initial collection on the class, which would be use unless overridden by a value provided in configuration:</p>
<script src="https://gist.github.com/AndyButland/780fb5096d6addd9b824b831e7474ef3.js"></script>
<p>Unfortunately this doesn't work as I expected as was pointed out to me in code review. If for example a single value was configured, it would replace the first value but then the second would still remain. There's <a href="https://levelup.gitconnected.com/overriding-an-array-configuration-object-4d93470e97d0">a good explanation here</a> of the problem, and why it is actually the expected behaviour.</p>
<p>One solution to this would probably be to change the configuration data structure, to use a dictionary. Unfortunately due to backward compatibility reasons, that wasn't an option in this case.</p>
<p>So I needed a different approach, needing to inpsect the value set from configuration, and depending on what they are, override with some sensible defaults. Which can be done, using <i>PostConfigure</i>. Like most topics with .NET, Andrew Lock has <a href="https://andrewlock.net/delaying-strongly-typed-options-configuration-using-postconfigure-in-asp-net-core/">a good blog post explaining this</a>.</p>
<p>The final code looked like this, which, as is needed, will check the configured values and update to the appropriate defaults if necessary:</p>
<script src="https://gist.github.com/AndyButland/d04ff33f5efa3b78ed9aeaaf9b5253a5.js"></script>
<p><i>PostConfigure</i> is a useful technique to have available when you want to make sure configuration provided by users is valid for your application, allowing you to hook into the process, once the configuration has been established but before it is used, to make any necessary updates.</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-27173980578412591142022-11-30T09:50:00.001+01:002022-11-30T09:51:39.640+01:00Handling Umbraco Events After Deploy Operations<p>I had a question from an partner Umbraco raised for me this week, that's available at <a href="https://our.umbraco.com/forum/using-umbraco-and-getting-started/110410-umbraco-cloud-deploy-not-triggering-dictionaryitemsavednotification">this forum topic</a>.</p>
<p>They had some code running in a notification handler running when dictionary items were saved, and found this wasn't firing when the item was saved following an Umbraco Deploy operation.</p>
<p>I responded that this is actually expected behaviour - as Deploy supresses events being fired on individual save and publish events when transferring items. This implementation decision was made I understand for performance reasons. In the back-office, the usual case for a save of an item is one-at-a-time, but with a deployment, there could be hundreds of updates within an operation.</p>
<p>However, what if you do want to run some code on the save of some Umbraco data, even if this is happening as part of a Deploy operation?</p>
<p>There's an option here using cache refresher notifications. Not all events are suppressed by Umbraco Deploy - some that are batched up and fired after the deploy operation is completed are those related to refreshing the Umbraco cache. These are necessary to ensure that in load balanced environments, all instances get their cache updated in the case for example of a content item being saved. When they run, they fire off notifications.</p>
<p>The following gist shows how one of these notifications can be hooked into and some custom code run.</p>
<p>There are a couple of checks in place to make sure we are handling the appropriate event - in this case a cache refresh of a single dictionary item. I've also ensured it only runs only once in load balanced environments.</p>
<script src="https://gist.github.com/AndyButland/5fcc6fbcaa89f3f879ab16d644014d8e.js"></script>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-85654800572807415502022-11-10T13:05:00.005+01:002022-11-10T13:06:38.321+01:00What Content Broke My Restore?<p>Just something I want to keep track of...</p>
<p>From a support issue with Umbraco Deploy on Umbraco 8, a customer was getting a failure when restoring, which stemmed from a JSON serialization failure when generating the Umbraco Deploy artifact. It was a bulk restore though, so tricky to figure out which content was causing the problem. And as this serialization is happening in the upstream environment being restored from, a local instance and debugging doesn't help.</p>
<p>This hacky code dumped in a view was useful to find out:</p>
<script src="https://gist.github.com/AndyButland/d01fc45813f6217a180c7f0deaf9d6cc.js"></script>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-78387357257450249322022-09-26T06:41:00.001+02:002022-09-26T06:41:29.239+02:00Cache Busting Back-Office Client-Side Assets With Umbraco 9+<p>Since I started at Umbraco the link I've shared the most when helping with support issues has been a blog post article by Dennis Adofi titled <a href="https://adolfi.dev/blog/umbraco-bump-cdf/">"Bumping The Client Dependency Version"</a>. It describes the method used to ensure caches are cleared for client-side assets in the back-office for Umbraco versions up to 8.</p>
<p>There's a similar setting for Umbraco 9+ which I've recently had to look up a few times. The configuration setting is at: <i>Umbraco : CMS : RuntimeMinification: Version</i> and is set to a numeric value.</p>
<p>It's documented <a href="https://our.umbraco.com/documentation/Reference/Configuration/RuntimeMinificationSettings/#manually-changing-the-cache-buster-version">here</a> and, as described, uses a similar version number that can be increment manually or via a build process to ensure the same cache busting behaviour.</p>
<p>When upgrading Umbraco to a new version of the CMS you don't need to worry about this, as the version running is already used in the hash that is generated for the cache key. But this doesn't take into account package installs, or your own custom extensions to the back-office.</p>
<p>So in you are "not seeing what you expect to see" on the client-side following an upgrade or a deploment, check to see if you have incremented this value, and if not, try to see if that resolves the issues you are having.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-14594602514052980232022-08-16T09:52:00.006+02:002022-08-16T10:54:11.143+02:00A quick post on view model mapping<p>I had a question about options for mapping Umbraco content to view models come up today, and given my response in Slack was getting a bit long and I'm sure I'll need to refer to it again, decided to create a post instead.</p>
<p>So what follows is a quick summary of the history and options for adopting a pattern of mapping Umbraco content to a strongly typed view model. It's not intended to be fully comprehensive... am just <a href="https://www.hanselman.com/blog/do-they-deserve-the-gift-of-your-keystrokes">saving my keystrokes</a>.</p>
<p>Around the time of Umbraco 6 or 7, some colleagues and I from <a href="https://www.zonedigital.com/">Zone</a> released <a href="https://our.umbraco.com/packages/developer-tools/umbraco-mapper"><b>Umbraco Mapper</b></a>, which used a combination of convention and configuration to allow for these mapping operations to be made in code. We used it in a fair few projects and liked the approach, and, judging from some feedback, a number of others from the Umbraco community did too.</p>
<p>Just after, though likely in use or development concurrently, was <a href="https://our.umbraco.com/packages/developer-tools/ditto"><b>Ditto</b></a> released by Lee Kelleher and Matt Brailsford. This did a similar job, and it gained a fair bit of traction in the community. It wasn't upgraded beyond Umbraco 7 though.</p>
<p>There were others too - <a href="https://github.com/JimBobSquarePants/UmbMapper"><b>UmbMapper</b></a> being one that looked to be the best performant. Again though I don't believe it was available beyond Umbraco 7.</p>
<p>Likely part of the reason these packages weren't continued was because Umbraco themselves introduced <a href="https://our.umbraco.com/documentation/reference/templating/modelsbuilder/"><b>models builder</b></a>. This also solves problems in the same space, but isn't <i>quite</i> the same - the subtle difference being that it provides a strongly typed "content model" rather than a "view model". For some this may not be a big issue, for others, including me, there was still some value in having a tool to help map Umbraco content direct to a view model designed specifically to represent a single page (which may be similar to a single item of Umbraco content, but likely won't be exactly the same).</p>
<p>Alongside all of this of course is the well-known .NET library <a href="https://automapper.org/"><b>Automapper</b></a>. However this doesn't know anything about Umbraco, so whilst it is useful for mapping between strongly typed models, it doesn't particularly help with mapping from Umbraco's <i>IPublishedContent/IPublishedElement</i>, without configuration for each individual property.</p>
<p>And it's probably worth mentioning you can still use strongly typed view models and not use a mapping library at all. You can see examples of this in the demo site that Dennis Adolfi blogged about <a href="https://umbraco.com/blog/putting-umbraco-9-to-the-test-dennis-adolfi/">here</a> (such as in the code available <a href="https://github.com/Adolfi/UmbracoNineDemoSite/tree/master/UmbracoNineDemoSite.Core/Features/Home">here</a>).</p>
<p>Now the story gets a bit (more?) confusing... as Umbraco introduced their own <a href="https://our.umbraco.com/documentation/reference/Mapping/">"UmbracoMapper"</a> class in core. Which was fair enough, it's their name and I probably shouldn't have stepped on their trademark in the first place! It provides mapping between types, a bit like AutoMapper but more lightweight, and with less convention. It can be used in Umbraco implementations too, but as I say, it doesn't offer any convention based mapping nor be designed to work specifically with <i>IPublishedContent/IPublishedElement</i>.</p>
<p>For that reason, when it came to Umbraco 9, I decided to keep the original "Umbraco Mapper" going, but renamed it to <a href="https://github.com/AndyButland/Anaximapper"><b>Anaximapper</b></a> to avoid confusion with the similarly named class in Umbraco itself. This is <a href="https://www.nuget.org/packages/Anaximapper">available from NuGet</a> and supports Umbraco 9 and 10.</p>
<p>Although I'm now working for Umbraco, this remains a personal, open-source project and isn't something supported (or necessarily even recommended... but I don't think they object!) by Umbraco themselves. As I'm no longer working at an agency developing Umbraco sites, it's not something I use directly any more either. But I am happy to keep it updated and supported in a personal capacity, so if you like the approach and find it valuable, you are welcome to continue using it, and if you run into any problems, issues raised and PRs are welcome.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com1tag:blogger.com,1999:blog-4689305754522303819.post-47943259035211605562022-07-12T14:59:00.006+02:002022-07-12T15:01:20.254+02:00Using SQL Profiler To Identify Repeated SQL Calls<p>This week I've been looking into some optimisations for a long-running process that chats to the database. Within the operation, the data isn't expected to change, so it would be reasonable that, where we make repeated calls, we cache the values and re-use them for the duration of the operation. I've been using SQL Profiler to identify them and wanted to blog the method for reference.</p>
<p>First, start up SQL Profiler and create a new trace. Connect to the database and define the Events Selection. I kept most things related to data reads and writes:</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNQ121XbYmX90m4mULG2Qjl3SESEDMoXNVUpguod9Hf_i757hBswlhdn-mNKKIp1dXADlUS_RC1_YZQOcQrbt78HSR_7ndwB-cca9r9NC-dqngtvyBITf_nfNJWmayM2X2Uq5eV1pHogIzSVX30ntmxQ0BtBFqqMAXVg6aaSDml7QgjfesXe8N-5rYhuvxsuDRkHY/s2107/sql-profiler-events.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="600" data-original-height="1350" data-original-width="2107" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNQ121XbYmX90m4mULG2Qjl3SESEDMoXNVUpguod9Hf_i757hBswlhdn-mNKKIp1dXADlUS_RC1_YZQOcQrbt78HSR_7ndwB-cca9r9NC-dqngtvyBITf_nfNJWmayM2X2Uq5eV1pHogIzSVX30ntmxQ0BtBFqqMAXVg6aaSDml7QgjfesXe8N-5rYhuvxsuDRkHY/s320/sql-profiler-events.png"/></a></div>
<p>I then ran the application, set a break-point in the code at the point where afterward I was interested in the results, and attached the debugger.</p>
<p>On hitting the breakpoint, I started the trace and continued the code execution. When the operation was complete I could stop the trace.</p>
<p>To analyse the data, I saved it to a new table in a database I created to store the results in, via <i>File > Save As... > Trace Table</i></p>
<p>I then created a view on the table, that filtered out some of the records and columns that weren't of interest:</p>
<pre style="font-size: 14px">
CREATE VIEW vwTraceData
AS
SELECT RowNumber,Convert(varchar(max),TextData) TextData,StartTime,EndTime
FROM TraceData
WHERE ApplicationName = '.Net SqlClient Data Provider'
AND TextData NOT LIKE '%--%'
AND TextData NOT LIKE '%exec sp_reset_connection%'
AND TextData NOT LIKE '%SET LOCK_TIMEOUT%'
</pre>
<p>The <i>TextData</i> column was converted to <i>varchar(max)</i> to allow joining from the column.</p>
<p>From the data it's possible to see which SQL calls are being repeated:</p>
<pre style="font-size: 14px">
SELECT TextData,Count(*) Occurances
FROM vwTraceData
GROUP BY TextData
ORDER BY Occurances DESC
</pre>
<p>And by repeating the operation and saving into a new table, I could compare to see if my optimisations had the necessary effect:</p>
<pre style="font-size: 14px">
SELECT v1.TextData,Count(*) V1Count,V2Count
FROM vwTraceData v1
INNER JOIN (
SELECT TextData,Count(*) V2Count
FROM vwTraceData2
GROUP BY TextData
) v2 ON v2.TextData = v1.TextData
GROUP BY v1.TextData,V2Count
</pre>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-27400609997361263972022-05-09T16:56:00.000+02:002022-05-09T16:56:04.596+02:00Customising the Umbraco Pipeline<p>The excuse for today's post is I need somewhere to write up some findings from an Umbraco support case I've been looking into. A customer was using an external library that provided middleware that needed to be configured between <i>UseRouting()</i> and <i>UseEndpoints()</i>. Which isn't a problem if you have full control over the contents of the <i>StartUp.cs</i> class, but it's not so easy when Umbraco is doing some of this setup for you.</p>
<p>Thanks to Benjamin Carleski from the Umbraco community who figured this out and shared it in <a href="https://our.umbraco.com/forum/umbraco-9/106433-enable-cors-on-umbraco-9">this forum conversation</a>. Nothing is really different from his answer on setting up CORS, but in this post I can more easily and permanently share the sample code.</p>
<p>Assuming we have a library that requires a typical "Add..." addition in the <i>ConfigureServices</i> method and "Use..." in the <i>Configure</i> method of <i>Startup.cs</i>.</p>
<p>First we need a pipeline filter class:</p>
<script src="https://gist.github.com/AndyButland/0fe08cc3272746bdd1c2e9cd619f1662.js"></script>
<p>Then a class responsible for inserting this filter in the pipeline:</p>
<script src="https://gist.github.com/AndyButland/e95af0e933011e068c337ac998e0f622.js"></script>
<p>And finally, in <i>Startup.cs</i>, <i>ConfigureServices</i> method, we can add:</p>
<script src="https://gist.github.com/AndyButland/75a5e0ab6459091cdc01ad935125387c.js"></script>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-74365602646864528432022-05-01T11:49:00.004+02:002022-05-01T11:49:51.215+02:00Pushing Updates to a PR<p>This is unashamedly a "note to self" post... something I occasionally need to do, and always need to ask about or look-up. As a maintainer of an open-source project on GitHub, an incoming PR from a contributor's fork that needs some minor changes before merging can be dealt with by committing changes directly to the branch on the fork, if the contributor has <a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork">allowed edits from maintainers</a>.</p>
<p>To checkout the branch locally:</p>
<pre>
git fetch origin pull/[pr id]/head:[branch name]
git checkout [branch name
</pre>
<p>After making changes, to push them back to the branch on the fork:</p>
<pre>
git remote add [remote name] addremote [fork url]
git push [remote name] [branch name]
</pre>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-89742138653593701432022-04-19T12:56:00.006+02:002022-09-19T16:39:17.505+02:00Getting Submitted Form Data in Umbraco Forms<p>With Umbraco Forms, you can configure a page to redirect the user to once a form has been submitted. A question came to me about how on that page we can get information about the form just submitted, as well as details of the record created from submission. I'll share the answer here for future reference.</p>
<p>We can make use of a couple of <i>TempData</i> values that are set automatically by Umbraco Forms (documentation <a href="https://our.umbraco.com/Documentation/Add-ons/UmbracoForms/Developer/Extending/#customizing-post-submission-behavior">here</a>).</p>
<ul>
<li><i>UmbracoFormSubmitted</i> - The Guid of the Form</li>
<li><i>Forms_Current_Record_id</i> - The Guid of the Record created from the form submission</li>
</ul>
<p>The following code example, valid for Umbraco 8, shows how this data can be retrieved. For illustration purposes, I've implemented it directly in the template for the page the user is presented with after the form is submitted. For production code, likely this would be better in the controller of a <a href="https://our.umbraco.com/documentation/reference/routing/custom-controllers">hijacked route</a>.</p>
<script src="https://gist.github.com/AndyButland/8af406cf0c564cf6ae9af124e645548e.js"></script>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-68126143040943367722022-03-18T10:00:00.002+01:002022-03-18T10:04:17.320+01:00Service Location in Umbraco 9<p>I should probably start this post with a caveat saying, "don't do this... unless you really, really have to!"</p>
<p>With Umbraco 9, built on .NET 5, we have <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">support for the dependency injection design pattern</a>, and where possible, the recommedation is to use that, to promote testability and other best practices.</p>
<p>Within the Umbraco CMS source code though, and also in the add-on products Forms and Deploy, we have found it necessary to use service location in a couple of instances. Primarily, to avoid breaking changes. For example, when a public class requires a new dependency, we don't just add it to the constructor as that would potentially break integrations that are already using it. Instead we obsolete the existing constructor, create a new one with the additional parameter, and have the original constructor delegate to the new one - using service location to provide the parameter for the added dependency.</p>
<p>In code it looks like this:</p>
<script src="https://gist.github.com/AndyButland/2f3072b7c8118c4c00e53fb4e95416c8.js"></script>
<p>To use, you need to be referencing Umbraco CMS 9.0.1 or higher (in 9.0.0 the <i>StaticServiceLocator</i> class was internal). And reference these two namespaces:</p>
<script src="https://gist.github.com/AndyButland/9765d0d8883d3e78aa07209e90c7f247.js"></script>
<p>The source for the <i>StaticServiceProvider</i> class is <a href="https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs">here</a>, where you can also read the comments discussing it's use cases. To reiterate, for most Umbraco projects there should be no need to use this. Instead dependency injection should be used, either directly for the component you need or via one of the "accessor" classes like <i>IUmbracoContextAccessor</i>. But if building a package, and similarly wanting to avoid breaking changes, it may be a useful technique to consider.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-72251075737437758452021-11-27T11:36:00.000+01:002021-11-27T11:36:07.657+01:00OAuth Integration for Umbraco Packages<p>In working through the development of an Umbraco integration recently, I had a need to setup an OAuth authentication process to secure access to an API. At first glance, not too tricky – the provider had a well documented process to follow. However I ran into a few challenges when considering how this process can work for an Umbraco package – i.e. an integration that’s not for a single website, but for many, and for ones we don’t know the domains for at the time of configuration.</p>
<p>All in all it seemed worth a blog post.</p>
<h3>The integration</h3>
<p>Specifically, the integration I was working on was one between Umbraco Forms and HubSpot, the CRM platform. We wanted to have a custom workflow that editors could add to a form, map the fields in the form to the fields defined on a “contact” record at HubSpot, and then, when a form is submitted, use the available APIs to map save the form submission data as a HubSpot contact.</p>
<p>My colleague Warren worked on the original integration, which we <a href="https://www.nuget.org/packages/Umbraco.Forms.Integrations.Crm.Hubspot">released</a>, <a href="https://github.com/umbraco/Umbraco.Forms.Integrations/tree/main/src/Umbraco.Forms.Integrations.Crm.Hubspot">open-sourced</a> and <a href="https://umbraco.com/blog/integrating-umbraco-forms-with-hubspot-crm/">blogged about how we built it</a>.</p>
<p>HubSpot offers <a href="https://developers.hubspot.com/docs/api/intro-to-auth">two authentication methods – using an API key and a OAuth process</a>. We adopted the former, which was easier to integrate and likely suitable for a machine-to-machine, server-side integration such as that we had built.</p>
<p>This option is less recommended by HubSpot though, and in order to have your integration listed at their marketplace, it’s mandated to use the OAuth route.</p>
<p>Hence for an update to the package, we looked to add an option for the customer to configure and use the OAuth approach, should they prefer that option.</p>
<h3>The OAuth prcess</h3>
<p>The steps required for setting up OAuth for Hubspot are <a href="https://developers.hubspot.com/docs/api/working-with-oauth">well documented</a>, and are likely familiar if you’ve worked on similar integrations previously, as other providers will also follow this standard.</p>
<p>In summary, we need to handle the following steps:</p>
<ul>
<li>Have the user link to a specific URL provided by the platform, from where they are asked to login to their HubSpot account and agree to the requirement that the integration requests.</li>
<ul>
<li>These requirements are configured as scopes, and for our integration we required the ability to read and write contact information.</li>
</ul>
<li>The user is then redirected to a configured URL, where an <b>authorization code</b> is provided.</li>
<li>This code can then be used in an API request to retrieve a pair of tokens – an <b>access token</b> and a <b>refresh token</b>.</li>
<li>The access token has an expiry, but can be used for a while for subsequent API requests, authorizing the access to the API calls and resources agreed to by the user.</li>
<li>The refresh token is longer lived, and can be used to request new access tokens, without going through the whole process again.</li>
</ul>
<h3>Adding OAuth to the package</h3>
<p>The first step to add OAuth integration is to create and configure an app on HubSpot. Here we can provide details such as a name, logo and description that a user browsing the marketplace of available integration will see.</p>
<p>We can retrieve a client id and client secret, of which one or both need to be provided in the API calls we make for authentication and subsequent operation of the package.</p>
<p>Finally we configure the URL that the user will be returned to once they have signed into their account and agreed to the requested scopes.</p>
<p>And it was here we hit the first snag – there can only be one redirect URL configured, and it needs to be known, not at runtime when the package is installed, but now, at the time of configuring the app on HubSpot. This wouldn’t be a problem if we were configuring an integration for a single website, but of course for a package or product, installable by clients, it’s for many websites, not one, all of which are for domains we don’t know about.</p>
<h4>Introducing a “stepping stone” website</h4>
<p>Our solution to this was to decide to build and host a website, that can be used to receive these post-authentication requests, and configure that as the redirect URL.</p>
<p>With that we could set up the following process:</p>
<ul>
<li>On first using the package, the user can be determined as being unauthenticated by checking for a configured API key or a stored refresh token. If neither exists, they aren’t authenticated.</li>
<li>We then prompt them to click off to the configured authentication URL.</li>
<li>Once they’ve logged in and agreed to the requested scopes, they’ll be sent to our intermediate website.</li>
<li>The authentication code is provided in a querystring, from where we can retrieve it and expose it clearly to the user so they can copy it to the clipboard.</li>
</ul>
<p>So now we have an authorization code, what can we do with it? My first though, quickly abandoned, was that we could just ask the website implementor to save this code to their configuration file – in a similar way as to how we handled the initial API key based integration. This isn’t an option though, as, unlike the API key, the authorization code is very short lived. We do need to quickly go through the process of using it to generate an access and refresh token.</p>
<p>Instead of that, we would ask the user to paste the code into a field exposed on the Umbraco backoffice, click a button and from their we can make the API request to HubSpot to get the access token and refresh token.</p>
<p>The refresh token we save into the Umbraco database using the <a href="https://our.umbraco.com/forum/using-umbraco-and-getting-started/103165-store-value-outside-the-application#comment-322381">IKeyValueService</a>. And the access token we save into an in-memory cache, such that we can use it in further API requests until it expires. If and when that happens, we can retrieve the refresh token from the database, get a new access token, retry the request and again cache the access token for future use.</p>
<h4>Protecting the client secret</h4>
<p>With that working, there was a further thing bothering me, which was that in the API requests made to retrieve tokens, we needed to provide the client secret in the request. And keeping this a secret seemed to be a problem.</p>
<p>As we’ve open sourced the integration, if we were to commit this key as a constant hard-coded into our application, it would be very easy to find it. Even if closed-source, via de-compiling it could still be discovered if someone was minded to do so.</p>
<p>Again we hit this problem as we are looking to build an integration package installable into multiple clients. If we had a single website to integrate with, we could just have this key safely in a configuration file or some other secrets store.</p>
<p>One option here would be to ask customers to set up their own HubSpot application. So rather than there being one that we configured, each project using the package would need to create their own. As well as being extra effort that we’d have to ask users of the package to make, it also meant we’d lose the ability to have an official Umbraco Forms app listed on the HubSpot marketplace.</p>
<p>The solution we found to this was to make use of the “stepping stone” website we already had in place. We exposed an API endpoint that our package token API requests would be configured to call, rather than going to the HubSpot URL. This API would be a relay to the HubSpot API, taking the request from the package, passing it on, and then similarly transferring the response back. You can see the source for this relay endpoint <a href="https://github.com/umbraco/Umbraco.Forms.Integrations/blob/main/src/Umbraco.Forms.Integrations.Crm.Hubspot.OAuthProxy/Controllers/OAuthProxyController.cs#L24">here</a>.</p>
<p>The only thing it would add to the request would be the client secret, that the package wouldn’t know about, but the relaying web application could. We have the client secret now stored in a protected variable in our Azure DevOps pipeline, which is written as an application setting to the deployed Azure Web App. And as such it’s known to the web application, but not the installed packages and client’s Umbraco websites.</p>
<h4>Smoothing out the flow</h4>
<p>Now we’ve got a process that works, and is secure, but it suffers from being a slightly clunky flow for the user. We’ve got to ask them to copy and paste a code from one browser tab to another to complete the authentication. Which perhaps isn’t a huge deal, but still, it would be nicer if this process was more seamless.</p>
<p>And it turns out we could do that, making use of the browser’s ability to communicate between windows. By changing our initial link that kicks off the process from a standard anchor tag to a client-side request that uses <i>window.open</i> we could get a reference to the opener from the website that receives the authorization code.</p>
<p>Via that we can send a message with the authorization code to the opening window like this:</p>
<pre><code>
if (window.opener && typeof opener.postMessage === 'function') {
opener.postMessage({
type: 'hubspot:oauth:success',
url: location.href,
code: '@Model.AuthorizationCode'
}, '*');
window.close();
}
</code></pre>
<p>And in the opening window, running the integration in the Umbraco backoffice, we can receive this message, and use the code in the same way as if the user had copied and pasted it, and clicked the button, in the original flow:</p>
<pre><code>
window.addEventListener("message", function (event) {
if (event.data.type === "hubspot:oauth:success") {
// Use event.data.code.
}
}, false);
</code></pre>
<h3>Summing up</h3>
<p>By following the process described in this post we’ve got to a point where we now have an integration that can be used either with API key or OAuth based authentication as the user chooses. There are some challenges when setting up the latter for package that will be installed in many different locations, but by using an intermediate website and proxying API requests we can still adhere to this protocol and provide a smooth integration process for the user.</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-12547447532793540802021-04-19T17:19:00.002+02:002021-05-16T08:58:50.789+02:00Umbraco Forms Migration to .NET Core<p>Over the past couple of weeks at Umbraco HQ we've been working on the migration of the <a href="https://our.umbraco.com/packages/developer-tools/umbraco-forms/">Forms package</a> to V9 and .NET Core. I've been blogging recently on the topic, where I've <a href="https://www.andybutland.dev/2021/03/umbraco-package-migration-to-net-core.html">shared articles</a> on various aspects of the work needed and changes found in migrating an open-source package. Unsurprisingly though, that didn't cover everything a package developer might encounter, so I thought it was worth an addendum or two on some further topics found in the Forms migration.</p>
<h3>Source Code and Branching</h3>
<p>Most developers with a package out for V8 are unlikely to be able to simply call it "done" and start a new repository for V9 - and Forms in no exception. Broadly we expect to align the package with the CMS <a href="https://umbraco.com/blog/announcement-support-lts-and-eol-for-umbraco-cms-cloud-and-packages/">plans for long-term support</a>, and so we'll still be making bug fixes and, for a while, adding features to the V8 version.</p>
<p>I suspect different developers will adopt different approaches here, so whilst your mileage may vary, this is how we've approached it.</p>
<p>Given the needs of ongoing support of V8, the way we've adopted is to <b>keep the code in the same source repository</b> and <b>create a separate branch for V9</b> - so we'll have <code>v8/dev</code> and <code>v9/dev</code> containing the latest code for these two versions respectively.</p>
<p>In order to do the first pass of the migration, we took it one project at a time - there are four within the Forms solution. For each one, we created a new, empty class library project targetting .NET 5 and referencing the latest nightly of the appropriate CMS reference: <code>Umbraco.Cms.Web.Common</code>, <code>Umbraco.Cms.Web.BackOffice</code> or <code>Umbraco.Cms.Web.Website</code> as needed. And then copied over class files a few at a time, and made the necessary changes such that they at least compile.</p>
<p>Mostly this involves adjusting namespaces and making updates to meet the occasional new syntax or approach of .NET Core. Uses of Umbraco's <code>Current</code>, often in static methods, were converted to services using dependency injection (more on this below). And any hard stuff was punted for later with some commenting out and <code>TODO</code>s!</p>
<p>Once complete, the original project was deleted from the solution and the new one renamed and moved such that all the file paths for those classes existing in both V8 and V9 versions were the same, and hence merging between the two branches is as simple as it can be. Depending on the changes it may not be easy, but at least things are aligned to support doing so. The intention moving forward is to merge up from V8 to V9, with less expectation of going back the other way. Hence any feature intended for both branches would likely first be built in V8, and merged up to V9. And over time we'd expect that to reduce to just being bug fixes or security patches that where appropriate are merged up, with new features living only in V9.</p>
<p>And then we start working through the <code>TODO</code>s. At the time of writing these are mostly done, so the known issues that needed to be handled are complete. As I <a href="https://twitter.com/andybutland/status/1382959258044616705">teased</a> last week, the Forms package now at least runs in the back-office. We of course need to now test the package thoroughly, front-end and back-office, and no doubt some further changes will be needed.</p>
<h3>Injecting Dependencies</h3>
<p>I discussed a few times in my previous blog posts about how the .NET Core paradigm is much more aligned with dependency injection, with using this approach seen as a best practice, pushing you toward the "pit of success" in creating testable, maintainable code. So, as mentioned, we took a few existing classes that made use of service location and converted them into service classes that could be injected into those classes that required them.</p>
<p>In working through the Forms migration though we came across a few nuances to this, mostly via my colleague Bjarke's code reviews(!), that seemed worth commenting on.</p>
<h4>Dependencies and Extension Methods</h4>
<p>An extension method, being a static method, can't take dependencies injected through the constructor and rather if it requires something to function, that something needs to be passed in as a method parameter. In the Forms code-base we had one of these - called <code>ParsePlaceholders()</code> and implemented as an extension method on strings - that's used to support the <a href="https://our.umbraco.com/documentation/add-ons/umbracoforms/developer/Magic-Strings/">"magic string" functionality</a> of Forms. It takes a string and replaces various placholders that may be in it with details derived from the current HTTP request, the current member, the current published content or dictionary values.</p>
<p>And as you'd imagine, that means a lot of dependencies! Initially I did just that, adding them as additional parameters to the extension method, passing them in from the calling code that will in turn have the dependencies necessary for parsing placholders injected into them via the constructor.</p>
<p>It was clear though in review that this had become a bit of a mess - the calling code now requiring constructor provided dependencies that it didn't need other than to support this extension method, and the method used in many places always with it's long list of arguments. If we ever needed another dependency, there would be a lot of updates to make.</p>
<p>So I'd suggest this is a smell to look out for - and if you find it consider doing what we did and refactor the extension methods to instance methods on to a service class, perhaps with an interface, that's registered with the dependency injection framework and injected to the classes that require this functionality.</p>
<p>In other words, instead of calling <code>var parsedValue = value.ParsePlacholders(...)</code> we now do <code>var parsedValue = _placholderParsingService(value)</code>.</p>
<h4>Handing Different Lifetimes of Dependencies</h4>
<p>Dependencies registered with the framework can have different <a href="https://dotnetcoretutorials.com/2017/03/25/net-core-dependency-injection-lifetimes-explained/">lifetimes</a> - transient, scoped or singleton. And one of the rules coming out of that is <a href="https://samwalpole.com/using-scoped-services-inside-singletons">"you can't injected scoped services into singletons"</a>.</p>
<p>We had a few cases where we seemingly needed to do that though, for example we had cases where we were looking to access <b>UmbracoHelper</b> and <b>IPublishedContentQuery</b> in this situation.</p>
<p>So whilst this might be a sign that a wider refactoring is in order, it is possible to access these dependencies via a sort of "semi-service location" - a term I've just made up - by accessing the HttpContext and finding the registered service.</p>
<p>In code, this means rather than doing this in a singleton registered class, which isn't allowed:</p>
<pre style="font-size: 14px">
private readonly UmbracoHelper _umbracoHelper;
public DictionaryHelper(UmbracoHelper umbracoHelper)
{
_umbracoHelper = umbracoHelper;
}
</pre>
<p>We can do this:</p>
<pre style="font-size: 14px">
private readonly IHttpContextAccessor _httpContextAccessor;
public DictionaryHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void MyMethod()
{
var umbracoHelper = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<UmbracoHelper>();
...
}
</pre>
<p>And just to add, this doesn't preclude unit testing the code, as the service provider can be wired up on a mocked HttpContext like this:</p>
<pre style="font-size: 14px">
var serviceProviderMock = new Mock<IServiceProvider>();
UmbracoHelper umbracoHelper = CreateFakeUmbracoHelper();
serviceProviderMock
.Setup(x => x.GetService(typeof(UmbracoHelper)))
.Returns(umbracoHelper);
</pre>
<h4>Umbraco's Extension Methods</h4>
<p>Worth noting here too a few minor changes to properties you may be familiar with in V8 that have moved to extension methods:</p>
<p>On <b>IPublishedContent</b>:</p>
<ul>
<li><code>.CreatorName</code> is now <code>.CreatorName()</code>
<li><code>.WriterName</code> is now <code>.WriterName()</code>
</ul>
<p>On <b>PublishedRequest</b>:</p>
<ul>
<li><code>.HasPublishedContent</code> is now <code>.HasPublishedContent()</code></li>
</ul>
<h3>Trees and Editors</h3>
<p>Other than for namespaces, creating <i>custom back-office trees</i> is broadly unchanged so will be familiar in V9. They continue to inherit from <code>TreeController</code> and require the same attributes as previously. The only other difference I noted was the use of <code>FormCollection</code> over <code>FormDataCollection</code> where this is needed in method parameters.</p>
<p>It's a similar story for API controllers supporting custom <b>editors</b> inheriting from <code>UmbracoAuthorizedJsonController</code>.</p>
<p>The only significant difference I could find here was on how authorization is handled. In V8 we add an attribute to lock down access to users that have permissions for a particular section: <code>[UmbracoApplicationAuthorize(appName: Constants.System.ApplicationAlias)]</code>.</p>
<p>With V9, instead we add a .NET Core Authorize attribute referencing the name of a policy: <code>[Authorize(Policy = Constants.AuthorizationPolicies.SectionAccessForms)]</code>. It's another example of where V9 is aligning with .NET Core standards where it can, rather than adding a layer of customisation.</p>
<p>The policy itself is registered at application startup, which we currently have within a composer (still available in V9). It leverages the <b>SectionRequirement</b> made available in core to actually check the user's permission to a section.</p>
<pre style="font-size: 14px">
public class UmbracoFormsComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
...
builder.Services.AddAuthorization(o => CreatePolicies(o, Cms.Core.Constants.Security.BackOfficeAuthenticationType));
}
private void CreatePolicies(AuthorizationOptions options, string backOfficeAuthenticationScheme) =>
options.AddPolicy(Constants.AuthorizationPolicies.SectionAccessForms, policy =>
{
policy.AuthenticationSchemes.Add(backOfficeAuthenticationScheme);
policy.Requirements.Add(new SectionRequirement(Cms.Core.Constants.Applications.Forms));
});
}
</pre>
<h3>Miscellaneous Others</h3>
<p>Just quickly wrapping up on a few other changes found in migrating the code.</p>
<h4>Security</h4>
<p>Where accessing the current user previously required:</p>
<pre style="font-size: 14px">
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public DictionaryHelper(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor;
}
public void MyMethod()
{
var currentUser = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
...
}
</pre>
<p>It's now via:
<pre style="font-size: 14px">
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public DictionaryHelper(IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
public void MyMethod()
{
var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
...
}
</pre>
<h4>Cache Refreshing</h4>
<p>The method <code>RefreshByPayload()</code> on <code>DistributedCache</code> is now <code>RefreshByPayload()</code>.</p>
<h3>Wrapping Up</h3>
<p>As mentioned, we're now mostly in a testing phase with Forms, seeing what issues we encounter at runtime and looking to resolve them. We're also considering some refactorings given we have some opportunity for breaking changes. We'll keep these to a minimum though, and really the only one under consideration above and beyond what is necessary is whether to simplify and only support form definitions being stored in the database moving forward. If you've any views on that, I'd welcome your <a href="https://github.com/umbraco/Umbraco.Forms.Issues/issues/540">comments</a>.</p>
Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-65354913248747405722021-03-29T07:40:00.009+02:002021-04-01T15:28:13.629+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Distributing and Wrapping Up<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-leaning-on-umbraco.html">Leaning on Umbraco</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-unit-tests.html">Migrating Tests</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-extension-methods.html">Extension Methods</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-migrations.html">Migrations</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-corewiring-up.html">Wiring It All Up</a></li>
<li>Distributing and Wrapping Up</li>
</ol>
<p><b>Update 29th March, 2021, 18:15</b> - I've updated this article to discuss how to properly handle the NuGet distribution of content files, which was a problem I was having and discussed in the first publication of this post. Thanks to Bjarke and Warren for sorting me out!</p>
<h3>Installing the Package</h3>
<p>This post should be the last one, as - good news - the package works now in V9, and you can try it out if you like, by doing the following.</p>
<ol>
<li>Create an Umbraco project based on the <a href="https://our.umbraco.com/documentation/UmbracoNetCoreUpdates?_ga=2.79216340.1531185510.1616405839-136370690.1614591976#steps-to-install-the-umbraco-dotnet-new-template">alpha 4 template</a>.</li>
<li>Install Umbraco and check the back-office is up and running.</li>
<li>Install the package - either via the VS.Net package console and <b>Install-Package UmbracoPersonalisationGroups -Version 3.0.0-alpha-2</b> or the CLI and <b>dotnet add package UmbracoPersonalisationGroups --version 3.0.0-alpha-2</b></li>
<li>Open up <b>Startup.cs</b> and add:
<ul>
<li>A using statement of <b>using Our.Umbraco.PersonalisationGroups;</b></li>
<li><i>.AddPersonalisationGroups(_config)</i> as the last extension method in <b>ConfigureServices()</b></li>
<li><i>.UsePersonalisationGroups()</i> as the last extension method in <b>Configure()</b></li>
</ul>
</li>
<li>Build and run.
</ol>
<p>You should find you can create and edit personalisation groups in the back-office:</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiw7-ezVGwMFqnm5mAHKVkMl3hmDVZGt7-4C4XJ_rkPtzwwjAZh6rPwb2qE4cZq_UFaCewpCo4T_JgrLXDSq2mm7Dp9Rf3vDo0J5wvRDqbSg1qp6VD3et7gHXWvB7Gx0EfXwuJlMS8z7PGxfhdx/s2710/back-office.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="750" data-original-height="1160" data-original-width="2710" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiw7-ezVGwMFqnm5mAHKVkMl3hmDVZGt7-4C4XJ_rkPtzwwjAZh6rPwb2qE4cZq_UFaCewpCo4T_JgrLXDSq2mm7Dp9Rf3vDo0J5wvRDqbSg1qp6VD3et7gHXWvB7Gx0EfXwuJlMS8z7PGxfhdx/s600/back-office.png"/></a></div>
<p>To run a simple test on the front-end, I've created a document type with a title and an instance of nested content. The nested content is based on an element type that just has a single text property as well as an a personalisation group picker (a data type installed with the package), that I've given the default alias of <b>personalisationGroups</b>.</p>
<p>I've then created a content item based on that document type, with three elements in the nested content - the first of which I've associated with the "Weekday Visitors" personalisation group shown above.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibigzRxJLwEzlVDN1IYtGD4i9wbcp_jss5NX4bygy1Lh3eGjuX6MDdp_fj_l7krG_TGmYuJIzNQvM1aof_0RRwDumd3sGzT6Uad7g50lDyjM43uqE7OEdSH_rEKuKgtlbrQcaHfnSZrNumIRiO/s2508/content.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="750" data-original-height="1254" data-original-width="2508" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibigzRxJLwEzlVDN1IYtGD4i9wbcp_jss5NX4bygy1Lh3eGjuX6MDdp_fj_l7krG_TGmYuJIzNQvM1aof_0RRwDumd3sGzT6Uad7g50lDyjM43uqE7OEdSH_rEKuKgtlbrQcaHfnSZrNumIRiO/s600/content.png"/></a></div>
<p>The template to render the page looks like this:</p>
<pre style="font-size: 13px">
@using Umbraco.Cms.Core.Models.PublishedContent;
@using Umbraco.Extensions;
@using Our.Umbraco.PersonalisationGroups.Services;
@inject IGroupMatchingService GroupMatchingService;
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
Layout = null;
}
<h1>@Model.Value("headline")</h1>
<ul>
@foreach(var item in Model.Value<IEnumerable<IPublishedElement>>("nestedItems")
.Where(x => x.ShowToVisitor(GroupMatchingService)))
{
<li>@item.Value("title")</li>
}
</ul>
</pre>
<p><i>Note 1:</i> See how as I discussed in an earlier post, as my <b>ShowToVisitor()</b> extension method requires access to logic in registered service, we can inject it into the view pass it on as a parameter to the extension method.</p>
<p><i>Note 2:</i> I'm not using Models Builder here, as I had some trouble with getting this to work with nested content - maybe an alpha bug - so I had to disable that in configuration and use the extension methods working with <b>IPublishedContent</b>. And then found an issue with the models builder configuration in <b>appSettings.json</b> not being picked up, which when I looked into, I found it had already been found and fixed, and so updated to the latest nightly. The fun of working with alpha software!).</p>
<p>And you should find that the first item in the list of nested elements only renders if the day of the week matches what's configured in the criteria. When the screenshot below was taken it was a Sunday... so the first item doesn't show as it's configured only for weekdays.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWoDuZxIzQiGvrsr3M7l10itSTl37NdaT1TC0GvsX8rnYnWbg3GPAZmQOzoKiTwEAe9qZmOVxanV_WJLr6DPdVdsCWnqr9i5xWyLXEN4-v7DrloeW9WemYmku_nQTi8hu5zyd8IwTmMXJXH-b2/s0/test.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="281" data-original-width="762" width="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWoDuZxIzQiGvrsr3M7l10itSTl37NdaT1TC0GvsX8rnYnWbg3GPAZmQOzoKiTwEAe9qZmOVxanV_WJLr6DPdVdsCWnqr9i5xWyLXEN4-v7DrloeW9WemYmku_nQTi8hu5zyd8IwTmMXJXH-b2/s0/test.png"/></a></div>
<h3>Creating the Package</h3>
<p>In order to prepare the NuGet package, I used the <a href="https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli">dotnet CLI</a> and the <b>dotnet pack</b> command, which worked after some amends to the .csproj file. This best reference for this is the <a href="https://our.umbraco.com/documentation/UmbracoNetCoreUpdates?_ga=2.183652710.1531185510.1616405839-136370690.1614591976#package-development">package template</a> made available by Umbraco, for creating and working on a new package for V9.</p>
<pre style="font-size: 13px">
<ItemGroup>
<Content Include="App_Plugins\PersonalisationGroups\**\*.*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<None Include="build\**\*.*">
<Pack>True</Pack>
<PackagePath>build</PackagePath>
</None>
</ItemGroup>
</pre>
<p>This change causes the non-compiled assets - i.e. the JavaScript and angularjs views - to be included in the generated NuGet package, and, importantly, for them to be restored to the required location when the package is installed.</p>
<p>After some local testing, I <a href="https://docs.microsoft.com/en-us/nuget/nuget-org/publish-a-package">pushed the package to nuget.org</a> via <b>dotnet nuget push UmbracoPersonalisationGroups.3.0.0-alpha-2.nupkg --api-key ... --source https://api.nuget.org/v3/index.json</b>.</p>
<h3>Odds and Sods</h3>
<p>In this section I'll just collate a things that may be of interest that I needed to resolve, some of which may be relevant to this package and not much else, but it could be that others might run into too.</p>
<h4>Running From Visual Studio</h4>
<p>This first one is a silly one... but I wasted some time on it so I'll share to maybe prevent someone else doing so too! Having installed my test web application, I could run it via the command line with <b>dotnet run</b>, but not through Visual Studio.</p>
<p>To use Kestrel when debugging, make sure you switch this setting from the default of IISExpress to the name of your web application (<a href="https://stackoverflow.com/questions/56609303/how-to-only-use-kestrel-in-net-core">hat-tip</a>):</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7fsnjggAxJQLP9wD11Sxb3L0KUw-RG_m84bOM2ccAGoYOAFOyFngaOHi_yFn__GV-N06eWNfnEAIcSGLPa6VaAg5lfKJd2CIup6rS8fCFxkXtyhPO1Ii-WBKi9qXSmh-K2ef1dfq6RfhRfD_F/s0/start-kestrel-vs.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="219" data-original-width="707" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7fsnjggAxJQLP9wD11Sxb3L0KUw-RG_m84bOM2ccAGoYOAFOyFngaOHi_yFn__GV-N06eWNfnEAIcSGLPa6VaAg5lfKJd2CIup6rS8fCFxkXtyhPO1Ii-WBKi9qXSmh-K2ef1dfq6RfhRfD_F/s0/start-kestrel-vs.png"/></a></div>
<h4>Activating Class Instances</h4>
<p>I mentioned in an earlier post an issue that I parked, which was where I'm using runtime assembly scanning to pick up all instances of an interface <i>IPersonalisationGroup</i>. The reason for this is that rather than making available only the criteria that the package ships with for constructing a persona, I also want to support those that might be created within an Umbraco solution, or in an add-on package.</p>
<p>The problem I had was that, having scanned and found all the instances, in order to use them, they need to be activated, which I was doing using: <i>Activator.CreateInstance(type) as IPersonalisationGroupCriteria</i>. This code is relying on there being a parameterless constructor, but now I've refactored to use dependency injection, there is no longer one of these. Whilst you can provide constructor parameters to this method, even if I knew all the types of them at runtime - which I don't - this would be difficult as they aren't all the same.</p>
<p>What I really wanted, was a method that will instantiate a type and look in the IoC container for registrations of any dependency it needs. And turns out, there is just the thing: <i>ActivatorUtilities.CreateInstance(serviceProvider, type) as IPersonalisationGroupCriteria</i>.</p>
<p>You can see this in use <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Services/CriteriaService.cs">here</a>, with <i>IServiceProvider</i> being injected into the class.</p>
<p><b>Update:</b> - I've not had chance to look at this yet to see if it's worth changing my implementation, but thanks to a comment from Kevin Jump, looks like there's some base support for this type of thing in Umbraco. If interested, see the example he shared with me <a href="https://twitter.com/KevinJump/status/1376441865596243969">here</a>.</p>
<h4>Abandoning Embedded Resources</h4>
<p>For the V7 and V8 versions of this package, I used embedded resources for all the client-side assets. Back through the mists of time, this <a href="http://www.nibble.be/?p=415">was a technique used</a> in Umbraco, but I haven't seen much reference to it recently, and it's certainly undocumented so perhaps not officially supported. I did like the idea of just having one dll to ship though, which is why I used it before. I struggled getting it to work with Umbraco 9 though, running into an issue with <a href="https://github.com/Shazwazza/Smidge">Smidge</a>, a dependency Umbraco takes for runtime bundling and minification for back-office assets. This was failing if it didn't find a physical file on disk.</p>
<p>Maybe that's fixable, but I also thought that as well as doing something that's likely off the beaten path, I'm also possibly going to be opting out of this bundling and minification, which doesn't seem a good idea. Hence I switched to use the <a href="https://our.umbraco.com/documentation/Tutorials/creating-a-property-editor/">documented way of creating a property editor</a>, which I'm pleased to say, does "just work" on Umbraco 9, with files installed in the content root (not the wwwroot) folder.</p>
<h4>Member Related Functionality</h4>
<p>Functionality related to members has been specifically called out as not yet complete and stable for the Umbraco V9 release, so I haven't focussed too much on that. What I can see and say though is that the member services remain, so I've been able to use them as before to get lists of member groups, types and fields - see for example <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Controllers/MemberController.cs">here</a>.</p>
<p>Knowing also that the membership work is looking to migrate to use ASP.NET Core Identity, we can also likely rely on functionality using on that to work. So I've tentatively implemented code like <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Providers/MemberGroup/UmbracoMemberGroupProvider.cs">this</a> (<a href="https://stackoverflow.com/a/21690079/489433">hat-tip</a>), used here to retrieve the roles for the current logged in members (which translate in Umbraco to member groups).</p>
<h4>Back-Office Consistency</h4>
<p>Don't look too closely at the styling used for the back-office components - there's a fair amount of inline styling in there and hacks to make things look and work the same across V7 and V8. Now the package is V9 only, I can remove much of that and look to use UI components that are known to be available in the version of Umbraco the package is running in, and, if needed, reference a single CSS file that I add to the package.manifest. I have fixing that up down as a a "to do".</p>
<p>It's worth being aware of these components though, that are <a href="https://our.umbraco.com/apidocs/v8/ui/#/api/">documented</a> and can be re-used in packages. With V9, the front-end of the Umbraco back-office isn't changing, so there should be no issues with starting or continuing to use these.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/cf4e04fa72e6a8231c15c49fcaff7852aa3d4a80/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-59025410514258561542021-03-27T09:03:00.004+01:002021-03-29T07:43:26.070+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Wiring It All Up<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-leaning-on-umbraco.html">Leaning on Umbraco</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-unit-tests.html">Migrating Tests</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-extension-methods.html">Extension Methods</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-migrations.html">Migrations</a></li>
<li>Wiring It All Up</li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-wrapping-up.html">Distributing and Wrapping Up</a></li>
</ol>
<h3>Dependency Registration and other Start-up Tasks</h3>
<p>At this point I've gone some package functionality that compiles and some unit tests that pass, but there's no way to actually run it yet and it won't work if somehow installed into an Umbraco V9 website. In order for that to happen I need to ensure that the services I've created, which are being constructor injected into various classes that need them, are registered with the IoC container. I've also got to make sure the package migration will run and some custom routes are in place for the controllers that the back-office UI will call (e.g. to get a list of available criteria to pick from when creating a personalisation group definition).</p>
<p>The Umbraco way of doing this since V8 is to use <a href="https://our.umbraco.com/documentation/implementation/composing/">composers and components</a>, and these are still available in V9. The more typical .NET Core way of integrating dependencies into an application though is by configuring the pipeline with code. For example, if you wanted to <a href="https://code-maze.com/automapper-net-core/">use a library like Automapper</a>, you would install from NuGet, and then in the <i>Startup.cs</i> file use extension methods provided by the library to hook-up the functionality. So I've decided to follow that.</p>
<p>As well as just doing so to match .NET Core conventions, I'm free to do so - at least for now - as I'm considering only providing a NuGet install for the package rather than being able to install directly into the back-office. If that support is desired for other packages, then the composer method will still be required, as the idea is that the package will work without having to write any C# code. But given I've loosened this restruction for my case, I don't need to worry about that.</p>
<p>It's worth sharing a discussion I had with Bjarke (who heads up the team for the Umbraco .NET Core transition) who pointed out that you can do both. Have a component that's not much more than a one-liner, calling the extension methods you provide to be used in <i>StartUp</i>. That way, there's default configuration that the package will use if installed from the back-office, but, if a developer wants to, they can add code to call your extension method and potentially provide options to override certain behaviour. If doing this though you'd need to take care the registration logic doesn't run twice, or is made idempotent so there's no harm if it does.</p>
<h4>Service Configuration</h4>
<p>If you've ever done anything with ASP.NET Core web applications you'll be familiar with a couple of methods in start-up that can be used to wire up your applications - whether it's the adding of framework functionaity such as MVC or applying something specific for your application. The first of these is <i>ConfigureServices(IServiceCollection services)</i>. To avoid what would otherwise likely become quite a big method, there's <a href="https://dotnetcoretutorials.com/2017/01/24/servicecollection-extension-pattern/">a pattern used of providing extension methods to adapt the service collection</a> provided as a parameter to the method. In order to provide options for different installations, Umbraco has a slight variation on this, via a builder pattern, in that they have one method that extends the service collection called <i>AddUmbraco()</i> which returns an <i>IUmbracoBuilder</i>, and then further extensions on that to <i>AddBackOffice()</i> and <i>AddWebsite()</i> - that you can see in the default templates for a new Umbraco V9 application.</p>
<p>Hence I've followed this, creating an extension method <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/ServicesConfiguration.cs">AddPersonalisationGroups()</a>, providing the already resolved application configuration as a parameter, and including it after Umbraco's <i>AddComposers()</i> builder extension:</p>
<pre style="font-size: 13px">
public void ConfigureServices(IServiceCollection services) =>
services.AddUmbraco(_env, _config)
.AddBackOffice()
.AddWebsite()
.AddComposers()
.AddPersonalisationGroups(_config)
.Build();
</pre>
<h5>Registering Configuration</h5>
<p>The first thing the extension method does is register the package configuration as a strongly-typed object with the IoC container. This is using standard .NET Core methods, where we retrieve the named configuration section and provide a typed representation of it - in my case, <i>PersonalisationGroupsConfig</i>, viewable <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Configuration/PersonalisationGroupsConfig.cs">here</a>. You'll see that for each configuration value I've made sure there's a default, which makes configuration of the package optional.</p>
<p>Should you wish to configure it for non-default behaviour though, Umbraco's configuration by default ships in an <i>appSettings.json</i> file, organised like this:</p>
<pre style="font-size: 13px">
{
"ConnectionStrings": {
"umbracoDbDSN": ""
},
"Serilog": {
...
},
"Umbraco": {
"CMS": {
...
}
}
}
</pre>
<p>The plan is that package configuration will also sit under the Umbraco element, so at some point an application using various packages, including this one, might look like this:</p>
<pre style="font-size: 13px">
{
"ConnectionStrings": {
"umbracoDbDSN": ""
},
"Serilog": {
...
},
"Umbraco": {
"CMS": {
...
},
"Forms": {
...
},
"PersonalisationGroups": {
...
}
}
}
</pre>
<p>With the configuration registered, it's now available to be resolved via the constructor of controller and other classes via <i>IOptions<PersonalisationGroups></i></p>
<p>One last point on configuration, which I've not had a need for but there are examples in core, is that it's possible to create validation classes for your configuration. By doing this we can ensure that the configuration provided via the JSON file is valid, and if it's not, our application wil fail early at boot time rather than later, and more obsurely, when the configuration is used. The core examples are found under <a href="https://github.com/umbraco/Umbraco-CMS/tree/netcore/dev/src/Umbraco.Core/Configuration/Models/Validation">Umbraco.Core/Configuration/Models/Validation</a> and are wired up with e.g.:</p>
<pre style="font-size: 13px">
builder.Services.AddSingleton<IValidateOptions<GlobalSettings>, GlobalSettingsValidator>();
</pre>
<h5>Registering Services</h5>
<p>After configuration, services are registered using Umbraco's light wrapper of the MSDI abstractions. For example I call <i>services.AddUnique<ICriteriaService, CriteriaService>()>;</i> to ensure only a single instances of my interface <i>ICriteriaService</i> is registered with the required implementation of <i>CriteriaService</i>. Any class requesting that interface as a dependency will receive a copy of the registered concrete class.</p>
<p>Note that by providing the configuration as a parameter to the extension method, I can use that to customise which services are registered:</p>
<pre style="font-size: 13px">
switch (configSection.GetValue<CountryCodeProvider>("CountryCodeProvider"))
{
case CountryCodeProvider.MaxMindDatabase:
services.AddUnique<ICountryCodeProvider, MaxMindCountryCodeFromIpProvider>();
break;
case CountryCodeProvider.CdnHeader:
services.AddUnique<ICountryCodeProvider, CdnHeaderCountryCodeProvider>();
break;
}
</pre>
<h4>Application Configuration</h4>
<p>Going back to <i>StartUp</i>, the second method we need to augment is <i>Configure(IApplicationBuilder app)</i>, which is where, now all services are registered, application start-up tasks can be triggered. Again, following the conventions of .NET Core and Umbraco - who for example have <i>app.UseUmbracoBackOffice()</i> and <i>app.UseUmbracoWebsite()</i> - I've created an extension method <i>UsePersonalisationGroups()</i>, viewable <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/AppConfiguration.cs">here</a>.</p>
<p>The first of two tasks I have is to setup routing for controllers, which is possible using similar similar code to what we used in .NET Framework, documented <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0">here</a>. I've got a "to do" here to figure out how to add, or if I still need namespace constraints, in case another package happened to use the same controller names, but otherwise this worked as expected.</p>
<p>The second task is to run the migration, which - following a bit of service location to get some Umbraco dependencies like <i>IScopeProvider</i>, necessary as we're in a static extenion method - we can execute here (see <i>ExecuteMigrationPlan()</i> in the class linked above).</p>
<p>Of course it didn't all work first time, but after I'd fixed the obvious errors I was left with one issue which was that it didn't seem possible to create, save and publish a content item at this time. That may be an alpha release bug that will need looking into; for now I just contented myself with saving and not publishing the content item, and leaving that last step for the editor.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/a9498775dc57bc0fa08d758ab952aa4fd538b60f/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-73743635817875915102021-03-25T09:44:00.002+01:002021-03-29T07:41:21.036+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Migrations<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-leaning-on-umbraco.html">Leaning on Umbraco</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-unit-tests.html">Migrating Tests</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-extension-methods.html">Extension Methods</a></li>
<li>Migrations</li>
<li><a href="/2021/03/umbraco-package-migration-to-net-corewiring-up.html">Wiring It All Up</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-wrapping-up.html">Distributing and Wrapping Up</a></li>
</ol>
<h3>Package Distribution</h3>
<p>There are at least a couple of ways to distribute Umbraco packages. The original - and still supported in Umbraco V9 - is to create a package through the back-office, selecting the files, schema and content that you wish to include - and saving that into zip file packaged according to a custom format. This file can be uploaded to <a href="https://our.umbraco.com">our.umbraco.com</a>, from where it can be downloaded by other users for install on their projects, or discovered and installed via the back-office.</p>
<p>The approach that's more common in the .NET space as a whole, is to distribute via NuGet. This has an advantage in terms of better support for dependencies. If there's another library your package depends on, you can define this link in the NuGet file specification.</p>
<p>As a developer and package user, it's this method that I would tend to use if available, so I'm minded to make that the only means of installing the new version of Personalisation Groups. Maybe that's me just feeling lazy at the moment thinking of just one distribution method to maintain, we'll see, but for now I'll need to make sure that NuGet will suffice as a means of distributing the package. The files are clearly no problem, but one benefit of the Umbraco package format is the possibility of including schema and content that will be installed along with the package files.</p>
<p>In order to support that via the NuGet scenario we need to have some code run after package installation, which can use the Umbraco APIs to create the necessary content types, data types and content. I've done this using a migration, which is a managed set of steps used by Umbraco CMS as well as HQ packages such as Umbraco Forms, mostly to carry out database schema changes between verisons. You can run any code though, so it looks like a good solution here.</p>
<h4>Using Migrations</h4>
<p>Migrations aren't a V9 thing - they exist and are used in V8 - but there's a few changes I ran into worth documenting.</p>
<h4>Implementing the Migration</h4>
<p>You can see the migration files themselves <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Migrations/PersonalisationGroupsMigrationPlan.cs">here</a> and <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Migrations/V_3_0_0/AddSchema.cs">here</a>. And just to caveat... as this whole effor so far mostly a porting of an existing working package, I'm perhaps doing some things a little backward - as I haven't actually got to the point of being able to install and run the code yet. Rather am just getting to a compiling state and with passing unit tests. As such there's likely some errors and perhaps misunderstandings, which I'll clear up as necessary in a later post.</p>
<p>The idea though is that you create a plan class inheriting from <i>MigrationPlan</i>, that references one or more migration steps that inherit from <i>MigrationBase</i>. Each migration step is associated with a key value that's stored in the Umbraco database, so it knows which migrations have run and which, due to an upgrade, need to be run on start-up.</p>
<h4>Testing the Migration</h4>
<p>I've also written a unit test on the migration, checking that the expected calls to save operations have been made to create the necessary schema and content. You can see it <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups.Tests/Migrations/V_3_0_0/AddSchemaTests.cs">here</a>. One thing worth noting is that I've taken a dependency for the test project on <b>Umbraco.Cms.Tests</b>, which provides some useful base classes and helpers for unit tests. In my case I'm making use of the "builders" available for creating stub model classes - see the <i>CreateRootFolderContent()</i> method - which work by creating sensible defaults for most properties but allowing you to set just what you need.</p>
<p>One thing you'll likely notice is that the migration classes take quite a lot of dependencies - 12 in my example. They don't always need this, but as I'm using Umbraco's services and also creating instances of property editors, all of which themselves have a number of dependencies, the number builds up. This is likely something to just get used to though, as it follows from the promotion of dependency injection we have with .NET Core and now Umbraco V9, that anything the class needs should be injected via the constructor.</p>
<h3>Miscellaneous Others</h3>
<p>Just a couple of other points worth noting in the migration since the last update:</p>
<ul>
<li>When implementing property editors in C# - see my example <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs">here</a> - there's a <i>PropertyEditorAsset</i> attribute that can be used to associated javascript and other client resources. Previously the first argument was an enum defined in a separate library, ClientDependency, but now it's native as <i>Umbraco.Cms.Core.WebAssets.AssetType</i>.</li>
<li>There's a few more parameters to pass into the constructor for creating a new property editor, which inherit from <i>DataEditor</i>. All are available from the DI container though and can just be passed in.
</ul>
<h3>Updaing to Alpha 4</h3>
<p>Oh, and the official <a href="https://umbraco.com/blog/alpha-4-release-of-umbraco-on-net-core/">alpha 4 is out</a>! So I updated to use this - upgrading from the version installed from the nightlies to that available on the Umbraco pre-releases NuGet feed at <b>https://www.myget.org/F/umbracoprereleases/api/v3/index.json</b>.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/3f6f759607dff26d19015e6fb9f10116e8cee341/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-315801960022336352021-03-23T15:28:00.004+01:002021-03-29T07:41:30.014+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Extension Methods<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-leaning-on-umbraco.html">Leaning on Umbraco</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-unit-tests.html">Migrating Tests</a></li>
<li>Extension Methods</li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-migrations.html">Migrations</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-corewiring-up.html">Wiring It All Up</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-wrapping-up.html">Distributing and Wrapping Up</a></li>
</ol>
<h3>Migrating Extension Methods</h3>
<p>The functionality of the Personalisation Groups package is exposed via some extension methods added to Umbraco's <i>IPublishedElement</i> and <i>UmbracoHelper</i>. For example, given a set of content elements drawn from child nodes, a multi-node tree picker or a nested content property, you can show just the ones relevant to the user using something like this:</p>
<pre>
@foreach (var post in Model.Content.Children
.Where(x => x.ShowToVisitor()))
{
<h2>@post.Name</h2>
}
</pre>
<p>Umbraco's rendering APIs haven't changed a great deal, so not much modification was needed for the immediate implementation of the extension methods, but when I started hooking into the package functionality itself a few questions were raised and decisions needed to be taken. These stemmed from, as discussed earlier, the stronger .NET Core emphasis on using dependency injection: providing what you need for a particular class via dependencies provided via the constructor. I've been adapting the code to better fit that paradigm, moving logic that I'd previously had in static methods into concrete classes, abstracted by an interface.</p>
<p>I'm fully signed up to that approach - whether it's leading to the pit of success for this package migration time will tell, but it certainly leads to a pit of <b>more testable code</b>, as I've already found by being able to put more functionality under unit tests that was tricky to do so before.</p>
<p>This does present a difficulty for extension methods though, which are static methods and hence can't hook up to the constructor injected dependency injection that the service classes I've created now use. It was a similar story with configuration. Previously I'd created and used a static wrapper class around the configuration values, giving me strongly typed access to them, but now these are injected via constructs like <i>IOptions<PersonsalistionGroupConfig></i>, again they aren't easily accessible via static methods such as extension methods <a href="https://stackoverflow.com/questions/48056104/access-configuration-settings-on-static-class-asp-core">There are ways</a>... but they feel a bit like going against the .NET Core paradigm shift - or at least, stronger emphasis - toward dependency injection..</p>
<p>To take a first step to resolve this situation, the first thing I've done is make the extension methods thinner, and delegate more functionality to the services layer. This resolves the problem with configuration, as now that's only needed in the service classes, where it can be injected in (see for example, <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Services/GroupMatchingService.cs">here</a>).</p>
<p>That stil leaves the issue though of how the methods available on this service are made available to the extension method. Newing it up is an option, but really defeats the purpose of using dependency injection. We could also consider service-location. But the most straightforward answers seems to be just to "pass it in" as a parmater to the method. By doing this, the extension method signature for the example shown above goes from this (for V8):</p>
<pre>
public static bool ShowToVisitor(this IPublishedElement content)
</pre>
<p>To this (V9):</p>
<pre>
public static bool ShowToVisitor(this IPublishedElement content,
IGroupMatchingService groupMatchingService)
</pre>
<p>Which means in the template, we need to using the feature of .NET Core allowing to inject services into views, and then pass it along from there:</p>
<pre>
@inject IGroupMatchingService GroupMatchingService;
@foreach (var post in Model.Content.Children
.Where(x => x.ShowToVisitor(GroupMatchingService)))
{
<h2>@post.Name</h2>
}
</pre>
<h3>So is this "friendlier"?</h3>
<p>This trade-off between best practice and testable code versus some additional complexity in the rendering layer is something Umbraco also have to consider, as they lean quite heavily on similar extension methods for the querying and rendering of content in templates. In order to try to provide the best of both worlds, they provide two versions of most of these methods. The first follows the practice of asking you to provide all the dependencies as parameters to the extension method, as I've shown above. The <a href="https://github.com/umbraco/Umbraco-CMS/pull/9970">second uses a service locator pattern to retrieve the dependency</a>, such that it doesn't need to be provided in the extension method siguature.</p>
<p>The former should certainly be used in all code where you can inject the necessary dependencies via the constructor - and I saw this myself as part of this migration work. I had a call to retrieve the value of a property using the extension method <i>Value(this IPublishedContent, string alias)</i>. Whilst this should work at runtime, it was failing in my unit test with an error of: <b>System.TypeInitializationException : The type initializer for 'Umbraco.Extensions.FriendlyPublishedContentExtensions' threw an exception.</b> The reason being that the service location couldn't run within the context of a test, as all the necessary setup wasn't in place. Switching to the "unfriendly" (need a better term...) verison - <i>Value(this IPublishedContent, IPublishedValueFallback publishedValueFallback, string alias)</i> - I could <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/main/PersonalisationGroups/Services/GroupMatchingService.cs#L18">gain access to the additional dependency via the constructor of the service</a>, and <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/5afe386222dbb00f1aafeac47fbb8550f6cdf632/PersonalisationGroups.Tests/Services/GroupMatchingServiceTests.cs#L342">provide a stub implementation for my test</a>.</p>
<p>The latter though is what we'd likely use in templates, and it's debateable whether I should provide similar "friendly" extension methods for this package, that uses service locaton to resolve the <i>IGroupMatchingService</i> and delegates to method taking all parameters via it's signature. There's a few hoops to do this though - and as Umbraco's service locator is internal, it would mean fully opting in to this approach and implementing it myself in the package. So have decided to leave this for now and accept the additional "boilerplate" in the rendering.</p>
<p>To keep this to a minimum I've limited the extra dependency to a single service, via a bit of refactoring to create a single exposed service that itself can take more injected dependencies as is required through the constructor.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/5afe386222dbb00f1aafeac47fbb8550f6cdf632/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-83417985961298385522021-03-22T15:15:00.006+01:002021-03-29T07:41:40.578+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Migrating Tests<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-leaning-on-umbraco.html">Leaning on Umbraco</a></li>
<li>Migrating Tests</li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-extension-methods.html">Extension Methods</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-migrations.html">Migrations</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-corewiring-up.html">Wiring It All Up</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-wrapping-up.html">Distributing and Wrapping Up</a></li>
</ol>
<h3>Adding a Tests Project</h3>
<p>Having ported a reasonable amount of the functionality from the Personalisation Groups package over to a version for Umbraco V9 running on .NET Core, I've created a tests project and migrated over most of the existing unit tests.</p>
<p>There wasn't too much required here that wasn't fixed with some find and replace. I created an NUnit test project, which aligns with what Umbraco use in the CMS core, but that isn't necessary and you can use what you prefer. I'd previously used <a href="https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest">MSTest</a> which still available in .NET Core. The only changes needed were changing the <b>TestClass</b> and <b>TestMethod</b> to <i>TestFixture</i> and <i>Test</i> respectively and amending some tests that used an <b>ExpectedException</b> attribute to instead use <i>Assert.Throws()</i>. I also added the <i>Moq</i> library from NuGet.</p>
<p>Now the package is using the standard pattern for configuration which is injecting a POCO representation of the configuration into classess that need it wrapped in an IOptions interface, I also need a way to make this available to test objects that depend on them when instantiated for test. This can be done via <i>Options.Create()</i>, as described by Mitch Valenta <a href="https://mitch.codes/net-core-manually-instantiating-ioptions-for-unit-testing/">here</a>.</p>
<p>I had one test that is probably trying to tell me I need to do some refactoring because I'm looking to test what probably should be a private method, which has been marked as internal to expose it for unit testing. But this kind of thing is sometimes needed, and in ASP.NET Framework we would add an attribute to indicate which other projects, such as test projects, should have access to be able to call internal methods. In .NET Core this information is added to the package .csproj project file:</p>
<pre>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>PersonalisationGroups.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</pre>
<p>Most tests are now passing. The ones that aren't will need a bit of thinking about. I've got some code that scans the assemblies to look for personalisation criteria - all of which will implement the interface <i>IPersonalisationGroupCriteria</i>. The package does this to allow custom solutions, or other packages, to add their own additional criteria for their needs. It then uses a call to <i>Activator CreateInstance</i> to instantiate each criteria object. I'm running into a problem because, now I've switched to use dependency injection throughout, I no longer have a "parameterless constructor" for this method to use. Will park this for now though pending wiring up all the dependencies with the Microsoft DI abstraction <i>IServiceCollection</i>, from where hopefully there will be a way to resolve what's needed for each class.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/7a6abfc0d70963c5314f58b62692d9cc7b84c93f/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0tag:blogger.com,1999:blog-4689305754522303819.post-81361133466036734122021-03-22T09:58:00.007+01:002021-04-01T15:28:57.997+02:00Umbraco Package Migration to .NET Core: Criteria Providers - Leaning on Umbraco<p>This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:</p>
<ol>
<li><a href="/2021/03/umbraco-package-migration-to-net-core.html">Introduction</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-false-start.html">A False Start</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-clean-start-controllers-services-configuration-caching.html">A Clean Start - Controllers, Services, Configuration and Caching</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-httpcontext.html">Criteria Providers - Working With HttpContext</a></li>
<li>Leaning on Umbraco</li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-unit-tests.html">Migrating Tests</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-extension-methods.html">Extension Methods</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-migrations.html">Migrations</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-corewiring-up.html">Wiring It All Up</a></li>
<li><a href="/2021/03/umbraco-package-migration-to-net-core-wrapping-up.html">Distributing and Wrapping Up</a></li>
</ol>
<h3>Amends After Review</h3>
<p>I received some useful pointers on what I've done so far in this package migration from Bjarke Berg, who heads up the .NET Core transition project at Umbraco. All of which I've now applied. The general theme from all of them is that there are more places where I could lean on existing functionality of Umbraco to help out.</p>
<h3>Umbraco Dependency</h3>
<p>The first step taken after creating a new project for the package was to reference the Umbraco NuGet package from the nightlies feed. He pointed out that rather than referencing <b>Umbraco.Cms.Core</b>, it would be better to take a reference on <i>Umbraco.Cms.Web.Common</i>, and by doing that, the .NET Core packages I'd previously added would come in automatically, as transitive dependencies of Umbraco. And be not having direct references, as a package developer, I won't have to explicitly update these every time Umbraco update it's dependencies.</p>
<p>In order to do this though, I had to update the target framework I'd selected from the package from <b>netstandard2.0</b>, to <i>net5.0</i>, to match what <i>Umbraco.Cms.Web.Common</i> targets. This is fine though. The only need to target a .NET standard version is for a library that is intended to work on both .NET Framework and .NET Core. For a package running on Umbraco V9, we'll only be on .NET Core, so it does no harm to target that in the first place.</p>
<p>Having done this, dependencies look like this:</p>
<pre>
<ItemGroup>
<PackageReference Include="MaxMind.GeoIP2" Version="4.0.1" />
<PackageReference Include="Umbraco.Cms.Web.Common" Version="9.0.0-alpha004.20210311.16" />
</ItemGroup>
</pre>
<h3>Umbraco Helpers</h3>
<p>Bjarke also pointed out a couple of places where I was using underlying .NET Core functionality, that Umbraco has already wrapped and made available via a simpler API or with some enhanced functionality. If looking to multi-target a package to two Umbraco versions, there might be an argument to avoid this, but for now there seems no reason not to use them - the package isn't going to be running anywhere of course other than within an Umbraco solution.</p>
<p>The first was in the replacement for uses of <b>Server.MapPath</b>, used to find the full path to a file on disk, for which I'd used <b>IHostingEnvironment</b> from <b>Microsoft.AspNetCore.Hosting</b> - which now I've updated to net5.0, I can see is actually depreciated for <b>IWebHostEnvironment</b>. Umbraco though has it's own <i>IHostingEnvironment</i> abstraction, found in <i>Umbraco.Cms.Core.Hosting</i>, which has handier methosd to use for map path operations - <i>MapPathWebRoot</i> and <i>MapPathContentRoot</i> - one of which I've used <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/blob/8a5046da8a1dc9af0e760b370d2cd59e02fb9ae8/PersonalisationGroups/Controllers/GeoLocationController.cs#L199">here</a>.</p>
<p>For an explanation of why there are two "roots", see Marius Schulz's explanation <a href="https://mariusschulz.com/blog/getting-the-web-root-path-and-the-content-root-path-in-asp-net-core">here</a>.</p>
<p>Secondly, for sessions, Umbraco provides a helper interface <i>ISessionManager</i>, found in <i>Umbraco.Cms.Core.Web</i>, and providing two methods for <i>GetSessionValue()</i> and <i>SetSessionValue()</i>. By using this I could simplify things a bit, removing my own session abstraction <b>ISessionProvider</b> as Umbraco is already providing an interface I can mock for unit tests. However I've actually kept my own here, which delegates to Umbraco's implementation. It's arguably at least one abstraction too many, but I like consistency! And this way all the "providers" in the package are similar in their implementation and naming.</p>
<h3>Following Along</h3>
<p>The repository containing the code for the migrated package is <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore">here</a>. At the time of writing, the state of the migrated code can be seen <a href="https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/tree/8a5046da8a1dc9af0e760b370d2cd59e02fb9ae8/PersonalisationGroups">using this link</a>.</p>Andy Butlandhttp://www.blogger.com/profile/02536557608655499040noreply@blogger.com0