<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastiaan Janssen</title>
    <description>The latest articles on DEV Community by Sebastiaan Janssen (@cultiv).</description>
    <link>https://dev.to/cultiv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F729121%2Fd5329c4c-cf15-4f75-b933-4153ad123fce.jpeg</url>
      <title>DEV Community: Sebastiaan Janssen</title>
      <link>https://dev.to/cultiv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cultiv"/>
    <language>en</language>
    <item>
      <title>Setting Up a New Umbraco Package Dev Environment with Umbraco.AI</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Thu, 19 Mar 2026 09:30:57 +0000</pubDate>
      <link>https://dev.to/cultiv/setting-up-a-new-umbraco-package-dev-environment-with-umbracoai-3iac</link>
      <guid>https://dev.to/cultiv/setting-up-a-new-umbraco-package-dev-environment-with-umbracoai-3iac</guid>
      <description>&lt;p&gt;This guide walks through setting up a new Umbraco package project called &lt;code&gt;Umbraco.Community.AI.Woowoo&lt;/code&gt; that extends &lt;a href="https://umbraco.com/marketplace/category/ai/" rel="noopener noreferrer"&gt;Umbraco.AI&lt;/a&gt;. Rather than starting from scratch with a blank solution, the goal is a proper package structure from day one: the right project layout, a frontend build pipeline, uSync, GitHub Actions for NuGet publishing, the works.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://github.com/LottePitcher/opinionated-package-starter" rel="noopener noreferrer"&gt;Lotte's Opinionated Package Starter Template&lt;/a&gt; comes in. Lotte has done the hard work of figuring out what a well-structured Umbraco package solution should look like, and turned it into a &lt;code&gt;dotnet new&lt;/code&gt; template. Pair that with Matt Brailsford's &lt;a href="https://mattbrailsford.dev/umbraco-ai-kitchen-sink-install" rel="noopener noreferrer"&gt;Umbraco.AI Kitchen Sink install guide&lt;/a&gt;, and you have everything you need to go from zero to a fully wired-up Umbraco.AI development environment.&lt;/p&gt;

&lt;p&gt;This post walks through the full setup, including the Linux-specific gotcha to watch out for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 10 SDK&lt;/strong&gt; -- Umbraco 17 targets .NET 10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js and npm&lt;/strong&gt; -- the package project uses Vite and TypeScript for its frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A GitHub account&lt;/strong&gt; -- the template wires up a GitHub remote and includes GitHub Actions for NuGet publishing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Install the template
&lt;/h2&gt;

&lt;p&gt;Install the package starter template from NuGet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Umbraco.Community.Templates.PackageStarter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output ending with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Success: Umbraco.Community.Templates.PackageStarter::X.X.X installed the following templates:
  umbracopackagestarter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What does this template give you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you scaffold from it, you get a complete two-project solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/AI.Woowoo/&lt;/code&gt; -- the package project, with Vite + TypeScript frontend (via the &lt;code&gt;umbraco-extension&lt;/code&gt; template) and a NuGet-configured &lt;code&gt;.csproj&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/AI.Woowoo.TestSite/&lt;/code&gt; -- an Umbraco 17 test site with uSync pre-configured&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/AI.Woowoo.sln&lt;/code&gt; -- the solution file, with the TestSite already referencing the package project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.github/workflows/release.yml&lt;/code&gt; -- a GitHub Action that publishes to NuGet when you push a version tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;umbraco-marketplace.json&lt;/code&gt;, README stubs, issue templates, and an MIT &lt;code&gt;LICENSE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a solid foundation for package development, covering all the scaffolding that would otherwise take hours to set up from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Scaffold the solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new umbracopackagestarter &lt;span class="nt"&gt;-n&lt;/span&gt; AI.Woowoo &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="s2"&gt;"Your Name"&lt;/span&gt; &lt;span class="nt"&gt;-gu&lt;/span&gt; YourGitHubUsername &lt;span class="nt"&gt;-gr&lt;/span&gt; Umbraco.Community.AI.Woowoo &lt;span class="nt"&gt;--allow-scripts&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each parameter does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-n AI.Woowoo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The base name for the solution. The template prepends &lt;code&gt;Umbraco.Community.&lt;/code&gt;, so the NuGet package ID becomes &lt;code&gt;Umbraco.Community.AI.Woowoo&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-an "Your Name"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Author name, used in the &lt;code&gt;.csproj&lt;/code&gt; and marketplace metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-gu YourGitHubUsername&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your GitHub username, used to construct the repo URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-gr Umbraco.Community.AI.Woowoo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The GitHub repository name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--allow-scripts yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypasses the security prompt for the post-creation script (&lt;code&gt;setup.cmd&lt;/code&gt;), which runs &lt;code&gt;npm install&lt;/code&gt; as part of the &lt;code&gt;umbraco-extension&lt;/code&gt; template setup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This will take a few minutes -- &lt;code&gt;npm install&lt;/code&gt; runs as part of the setup. Wait for the "All done!" message.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: The Linux/macOS gotcha -- &lt;code&gt;setup.cmd&lt;/code&gt; won't run
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you're on Windows, you can skip this section.&lt;/strong&gt; The template's &lt;code&gt;setup.cmd&lt;/code&gt; script will have run fine and your solution is ready. Jump straight to Step 4.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On Linux and macOS, the &lt;code&gt;setup.cmd&lt;/code&gt; post-creation script fails with "Permission denied" because it's a Windows batch file. The template &lt;em&gt;files&lt;/em&gt; are all created correctly -- the script just doesn't run, so the git repository, the frontend scaffolding, the solution wiring, and a few other things need to be done manually.&lt;/p&gt;

&lt;p&gt;Here's everything the script would have done, translated into commands you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialise the git repository&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;AI.Woowoo
git init
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git remote add origin https://github.com/YourGitHubUsername/Umbraco.Community.AI.Woowoo.git

&lt;span class="c"&gt;# Make sure you have the latest Umbraco dotnet templates&lt;/span&gt;
dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Umbraco.Templates &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Scaffold the package project inside src/&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;src
dotnet new umbraco-extension &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"AI.Woowoo"&lt;/span&gt; &lt;span class="nt"&gt;--site-domain&lt;/span&gt; &lt;span class="s2"&gt;"https://localhost:44361"&lt;/span&gt; &lt;span class="nt"&gt;--include-example&lt;/span&gt;

&lt;span class="c"&gt;# The template creates two .csproj files -- swap them so the NuGet-configured one is active&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;AI.Woowoo
&lt;span class="nb"&gt;rm &lt;/span&gt;AI.Woowoo.csproj
&lt;span class="nb"&gt;mv &lt;/span&gt;AI.Woowoo_nuget.csproj AI.Woowoo.csproj

&lt;span class="c"&gt;# Wire everything up in the solution&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
dotnet sln add &lt;span class="s2"&gt;"AI.Woowoo"&lt;/span&gt;
dotnet add &lt;span class="s2"&gt;"AI.Woowoo.TestSite/AI.Woowoo.TestSite.csproj"&lt;/span&gt; reference &lt;span class="s2"&gt;"AI.Woowoo/AI.Woowoo.csproj"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; The repo root also contains a &lt;code&gt;setup.mjs&lt;/code&gt; file -- a Node.js replacement for &lt;code&gt;setup.cmd&lt;/code&gt; that works cross-platform. If it's present, running &lt;code&gt;node setup.mjs&lt;/code&gt; handles all the steps above.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4: Build the frontend &amp;amp; verify the base site
&lt;/h2&gt;

&lt;p&gt;Before layering anything on top, confirm the scaffolded solution actually works. Build the frontend assets first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;src/AI.Woowoo/Client
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see Vite output with files written to &lt;code&gt;dist/&lt;/code&gt;. Now start the TestSite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../../AI.Woowoo.TestSite
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;launchSettings.json&lt;/code&gt; included by the template automatically sets &lt;code&gt;ASPNETCORE_ENVIRONMENT=Development&lt;/code&gt;, so you don't need to set it yourself. On first run, Umbraco installs itself unattended -- watch the terminal output for the URL (typically &lt;code&gt;https://localhost:44331&lt;/code&gt; or similar).&lt;/p&gt;

&lt;p&gt;Log in with the pre-configured credentials from &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;code&gt;admin@example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password:&lt;/strong&gt; &lt;code&gt;1234567890&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Content&lt;/strong&gt; section -- an "Example Dashboard" should be there (this is the example added by the &lt;code&gt;--include-example&lt;/code&gt; flag in the &lt;code&gt;umbraco-extension&lt;/code&gt; template)&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;/umbraco/swagger&lt;/code&gt; -- the &lt;strong&gt;Umbraco Management API&lt;/strong&gt; document should appear in the dropdown&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once everything looks good, hit &lt;code&gt;Ctrl+C&lt;/code&gt; to stop the site. You now have a clean, working base to build on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Add the Umbraco.AI packages
&lt;/h2&gt;

&lt;p&gt;All the Umbraco.AI packages go into the TestSite project, not the package project. The package project stays clean -- it will only reference &lt;code&gt;Umbraco.AI&lt;/code&gt; (or specific Umbraco.AI interfaces) when actually needed for package code. The TestSite is the development host.&lt;/p&gt;

&lt;p&gt;Run these from the repo root (&lt;code&gt;AI.Woowoo/&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The Clean starter kit adds a demo content structure to the site&lt;/span&gt;
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Clean

&lt;span class="c"&gt;# The core Umbraco.AI integration layer&lt;/span&gt;
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI

&lt;span class="c"&gt;# Addons&lt;/span&gt;
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Prompt
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Agent
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Agent.Copilot &lt;span class="nt"&gt;--prerelease&lt;/span&gt;

&lt;span class="c"&gt;# AI providers&lt;/span&gt;
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Amazon
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Anthropic
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.Google
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.MicrosoftFoundry
dotnet add &lt;span class="s2"&gt;"src/AI.Woowoo.TestSite"&lt;/span&gt; package Umbraco.AI.OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Umbraco.AI.Agent.Copilot&lt;/code&gt; requires &lt;code&gt;--prerelease&lt;/code&gt; because it's still in alpha. The other packages are stable.&lt;/li&gt;
&lt;li&gt;All five provider packages (Amazon Bedrock, Anthropic, Google, Microsoft Foundry, OpenAI) are installed so the environment is ready for any AI provider you or contributors want to test against. The seed data in the next step only activates the OpenAI connection, but the others are configured and waiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check &lt;a href="https://mattbrailsford.dev/umbraco-ai-kitchen-sink-install" rel="noopener noreferrer"&gt;Matt's install guide&lt;/a&gt; if you're following along later -- version numbers may have moved on. The &lt;a href="https://github.com/umbraco/Umbraco.AI/tree/main/docs/public" rel="noopener noreferrer"&gt;Umbraco.AI documentation&lt;/a&gt; covers all the extensibility points in detail if you want to go further.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Add the seed demo data
&lt;/h2&gt;

&lt;p&gt;Matt has a helpful GitHub Gist that seeds a full Umbraco.AI demo configuration: a connection, profile, context, prompts, and agents. It's a self-registering C# file -- drop it in the project and it registers itself via Umbraco's component system and seeds the data on first startup. It's also idempotent, so safe to leave in place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; src/AI.Woowoo.TestSite/UmbracoAISeedData.cs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://gist.githubusercontent.com/mattbrailsford/199d0b45e926ffb122fa96467039bd90/raw/UmbracoAISeedData.cs?v=2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API key (optional)
&lt;/h3&gt;

&lt;p&gt;The seed data installs an OpenAI connection with a dummy key. The site runs fine without a real key -- all the AI configuration gets seeded into the backoffice, the connection just won't be functional. Replace the key later through the Umbraco.AI section in the backoffice once everything is up and running.&lt;/p&gt;

&lt;p&gt;Don't have an OpenAI key? Google hands out free Gemini API keys via &lt;a href="https://aistudio.google.com/app/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; -- no credit card required. Hit "Create API key" in the top right, or "Get API key" in the left sidebar. The free tier is generous enough for development. If you go this route, use the &lt;code&gt;Umbraco.AI.Google&lt;/code&gt; provider and configure the connection in the backoffice accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Run and verify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;src/AI.Woowoo.TestSite
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's running, log in (&lt;code&gt;admin@example.com&lt;/code&gt; / &lt;code&gt;1234567890&lt;/code&gt;) and navigate to the &lt;strong&gt;Umbraco.AI&lt;/strong&gt; section in the backoffice. You should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A seeded OpenAI connection&lt;/li&gt;
&lt;li&gt;A profile&lt;/li&gt;
&lt;li&gt;A context&lt;/li&gt;
&lt;li&gt;Several prompts&lt;/li&gt;
&lt;li&gt;Several agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you added a real API key, try running a prompt or agent to confirm the connection is live.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Make the initial commit
&lt;/h2&gt;

&lt;p&gt;Before staging everything, do a quick sanity check: open &lt;code&gt;UmbracoAISeedData.cs&lt;/code&gt; and confirm there's no real API key in there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"chore: initial scaffold from Lotte's Opinionated Package Starter Template"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; before pushing, go to GitHub and create a new empty repository named &lt;code&gt;Umbraco.Community.AI.Woowoo&lt;/code&gt; (skip the readme, license, and gitignore options -- the template already created those locally). The template already configured the remote, so &lt;code&gt;git push&lt;/code&gt; goes straight to the right place.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The environment is now ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/AI.Woowoo/&lt;/code&gt; -- the package project, clean and ready for package code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/AI.Woowoo.TestSite/&lt;/code&gt; -- a full Umbraco 17 + Umbraco.AI host to develop and test against, with demo data seeded&lt;/li&gt;
&lt;li&gt;GitHub Actions configured for NuGet publishing on version tags&lt;/li&gt;
&lt;li&gt;All five AI providers installed and ready to configure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it comes to implementing the package itself, the first thing to add to &lt;code&gt;src/AI.Woowoo/AI.Woowoo.csproj&lt;/code&gt; is a reference to &lt;code&gt;Umbraco.AI.Core&lt;/code&gt; -- the package that contains all the extension points (custom tools, middleware, guardrail evaluators, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add src/AI.Woowoo package Umbraco.AI.Core &lt;span class="nt"&gt;--version&lt;/span&gt; 1.6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;Umbraco.AI.Core&lt;/code&gt; 1.6.0 requires Umbraco.Cms 17.1.0 or higher. The template scaffolds the package project against 17.0.0, so bump the four Umbraco.Cms references in the same file to 17.1.0 when you add this.&lt;/p&gt;

&lt;p&gt;The next step is implementing the &lt;code&gt;Umbraco.Community.AI.Woowoo&lt;/code&gt; package itself -- but that's a story for another post.&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Using JWT tokens to access an UmbracoApiController</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Mon, 24 Jul 2023 10:46:53 +0000</pubDate>
      <link>https://dev.to/cultiv/using-jwt-tokens-to-access-an-umbracoapicontroller-3f88</link>
      <guid>https://dev.to/cultiv/using-jwt-tokens-to-access-an-umbracoapicontroller-3f88</guid>
      <description>&lt;p&gt;So far, JWT has been eluding me, I haven't had the time to look into them, I just knew they existed and worked by sending a header. &lt;/p&gt;

&lt;p&gt;Today, I was following a &lt;a href="https://www.linen.dev/s/umbraco/t/13262507/what-is-the-correct-way-to-implement-a-umbracoapicontroller-"&gt;question from Navorski&lt;/a&gt; about using JWT authentication on an &lt;code&gt;UmbracoApiController&lt;/code&gt;, I enjoyed the responses &lt;a href="https://dev.to/d_inventor"&gt;from Dennis&lt;/a&gt; who had clearly done this before.&lt;/p&gt;

&lt;p&gt;It seemed like this was easy enough to follow, so I decided to try it out for myself and see how far I could get. As it turns out, I was fearing them more than I should have done! Let's have a look.&lt;/p&gt;

&lt;p&gt;First we need to tell our Web App (Umbraco) that JWT is one of the way to authenticate. For that, we need to install a NuGet package first to help us out with that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next thing to do is to set up some configuration items, which can be added after the &lt;code&gt;"Umbraco":&lt;/code&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"Jwt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ihuEAzo7BxW09LTTNKCz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:44336/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:44336/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the appSettings.json it looks like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s7Jjr-HQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4r3yw3wcf0k5tvyauu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s7Jjr-HQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4r3yw3wcf0k5tvyauu9.png" alt="Screenshot of the config addition to the appSettings.json file" width="594" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Key&lt;/code&gt; I generated here is a secret, please don't copy it but create a unique key for yourself.&lt;/p&gt;

&lt;p&gt;Once that is installed, the &lt;a href="https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/"&gt;linked tutorial&lt;/a&gt; tells us to add some things to &lt;code&gt;Startup.cs&lt;/code&gt;. Personally, I like adding things using an &lt;code&gt;IComposer&lt;/code&gt; to make my code more portable in case I want to reuse it or put it in a package, for example.&lt;/p&gt;

&lt;p&gt;So I end up with the following code, that does the same thing as it would have done if I had added it to &lt;code&gt;Startup.cs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Umbraco.Cms.Core.Composing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;JwtTest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Issuer"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Audience"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;IssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Key"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever we request JWT token authentication, this code will run.&lt;/p&gt;

&lt;p&gt;Next up, we can make an &lt;code&gt;UmbracoApiController&lt;/code&gt; with an example endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Umbraco.Cms.Web.Common.Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;JwtTest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestApiController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UmbracoApiController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAuthenticatedMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"You need to be authenticated to see this message."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we run the site and go to &lt;code&gt;https://localhost:44336/umbraco/api/TestApi/GetAuthenticatedMessage&lt;/code&gt;, we see the message &lt;code&gt;You need to be authenticated to see this message.&lt;/code&gt;. Great! But the message is a lie, we didn't request or provide any authentication. Let's add some.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Umbraco.Cms.Web.Common.Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;v12final&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestApiController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UmbracoApiController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthenticationSchemes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAuthenticatedMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"You need to be authenticated to see this message."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Authorize&lt;/code&gt; attribute now requires us to provide a JWT bearer token in order for the request to succeed. When we now try to access the same endpoint, we get a nice &lt;code&gt;401 Unauthorized&lt;/code&gt; error. &lt;/p&gt;

&lt;p&gt;We're getting there! We need a way to obtain a JWT authorization token now. Normally you'd do that by verifying a user is authenticated in the system, I'll leave that part as a exercise to the reader and focus on generating the token we need. So in the next snippet of code, we allow anyone who provides us with any username and password to get a token. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Don't do this in real life, make sure you only hand out tokens to trusted parties, verify you can trust them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The following code was added to the same &lt;code&gt;TestApiController&lt;/code&gt; we had above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TestApiController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateJwtToken&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;UserModel&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: Do something to verify username/password&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;securityKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SymmetricSecurityKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Key"&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SigningCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;securityKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SecurityAlgorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HmacSha256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClaimTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NameIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClaimTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"apiuser"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtSecurityToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Issuer"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Jwt:Audience"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;signingCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtSecurityTokenHandler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WriteToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've also added the following &lt;code&gt;using&lt;/code&gt;s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IdentityModel.Tokens.Jwt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Security.Claims&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're injecting an &lt;code&gt;IConfiguration&lt;/code&gt; because we need some values from &lt;code&gt;appSettings&lt;/code&gt; and other than that it's mostly copied code from the tutorial. As said, there's no verification of any credentials, we just generate a token and spit it out. &lt;/p&gt;

&lt;p&gt;Now let's try it out in Postman by sending a &lt;code&gt;POST&lt;/code&gt; to the new endpoint at &lt;code&gt;https://localhost:44336/umbraco/api/TestApi/GenerateJwtToken&lt;/code&gt;, adding a username and password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PoPmX7sl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9c79yikiz1oljumofuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PoPmX7sl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9c79yikiz1oljumofuh.png" alt="Example of the request in Postman, which returns the JWT token we need" width="763" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Success! We get a token that we can hopefully use in the &lt;code&gt;GetAuthenticatedMessage&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Going back to Postman, we can add a header to the &lt;code&gt;GetAuthenticatedMessage&lt;/code&gt; request named &lt;code&gt;Authorization&lt;/code&gt; and as the value we add &lt;code&gt;Bearer&lt;/code&gt;, then a space and then the token we received from the &lt;code&gt;GenerateJwtToken&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xoxFjvVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wrirfvdubtphimntqxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xoxFjvVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wrirfvdubtphimntqxh.png" alt="Example of the request in Postman, which adds the Bearer token and shows the expected success response" width="660" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as you can see in the response body, we do indeed get our message back, this time it's not lying to us: &lt;code&gt;You need to be authenticated to see this message.&lt;/code&gt;. Correct!&lt;/p&gt;

&lt;p&gt;And there you have it, JWT tokens demystified (for me at least 😅).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Create a plain dotnet, routed API controller in Umbraco</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Mon, 07 Nov 2022 08:38:14 +0000</pubDate>
      <link>https://dev.to/cultiv/create-a-plain-dotnet-routed-api-controller-in-umbraco-2g1o</link>
      <guid>https://dev.to/cultiv/create-a-plain-dotnet-routed-api-controller-in-umbraco-2g1o</guid>
      <description>&lt;p&gt;This is a very quick blog post, blink and you'll miss it! 😉&lt;/p&gt;

&lt;p&gt;While building something the other day I suddenly had the need for a route that would output some JSON data. I didn't need anything from Umbraco, just some plain old (modern!) dotnet - yes this is for Umbraco 9+. &lt;/p&gt;

&lt;p&gt;I thought I'd have to register routes in &lt;code&gt;Startup.cs&lt;/code&gt; and that I'd have to add a route to &lt;code&gt;ReservedPaths&lt;/code&gt; in &lt;code&gt;appSettings.json&lt;/code&gt;, but.. to my surprise: none of that is needed, nice and easy and contained in 1 single &lt;code&gt;.cs&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;So without further ado, here's the example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Cultiv.Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetMeController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ActionResult&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Introduction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JsonResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Introduction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part here is the &lt;code&gt;Route&lt;/code&gt; attribute, where &lt;code&gt;[controller]&lt;/code&gt; will be replaced with the controller name, &lt;code&gt;GreetMe&lt;/code&gt;, so the route becomes &lt;code&gt;api/GreetMe&lt;/code&gt; and the &lt;code&gt;Get&lt;/code&gt; method is what will respond to any &lt;code&gt;Get&lt;/code&gt; request coming in. &lt;/p&gt;

&lt;p&gt;This results in a nice JSON string response with the proper &lt;code&gt;application/json&lt;/code&gt; response header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d71pfFPu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uu65owt6exr3ou2codi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d71pfFPu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uu65owt6exr3ou2codi7.png" alt="Image description" width="880" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is called attribute routing and you can do a LOT more with it, &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-for-rest-apis"&gt;check out the Microsoft documentation&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;That's it, that's the post! Just wanted to save it here to my external brain so I can find it in the future, hopefully it will help someone else as well.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>TIL: mime types and static assets in Umbraco</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Fri, 21 Oct 2022 08:37:29 +0000</pubDate>
      <link>https://dev.to/cultiv/til-mime-types-and-static-assets-bdj</link>
      <guid>https://dev.to/cultiv/til-mime-types-and-static-assets-bdj</guid>
      <description>&lt;p&gt;A few months ago I &lt;a href="https://dev.to/cultiv/umbraco-discord-wrap-up-week-18-c2j"&gt;wrote a post&lt;/a&gt; about learnings from the &lt;a href="https://discord.gg/umbraco"&gt;Umbraco Discord&lt;/a&gt; that week and then.. it went quiet 😅&lt;/p&gt;

&lt;p&gt;With the best of intentions to keep that series going, it turns out there's just not enough hours in the day! I have ideas though to open up the information from Discord more but that will have to be bit more long term. &lt;/p&gt;

&lt;p&gt;In the mean time, we did go down a fun exploration path with &lt;a href="https://twitter.com/deanleigh"&gt;Dean Leigh&lt;/a&gt; yesterday thinking about adding custom MIME types to Umbraco and overriding existing ones if needed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://discord.com/channels/869656431308189746/882984410432012360/1032544330591785051"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ovDtq5Mw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bmvw1ix2av7tzd1qyp84.png" alt="Screnshot of a Discord message where Dean is asking how to change MIME types" width="741" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://discord.com/channels/869656431308189746/1032551319648612374"&gt;turned into a thread&lt;/a&gt; where explored the actual needs and some solutions. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Program.cs&lt;/code&gt; vs &lt;code&gt;Startup.cs&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First off, I understand the confusion, Umbraco 9 was built on .net 5 and that required us to have &lt;code&gt;Startup.cs&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;In .net 6, Microsoft consolidated the two into &lt;code&gt;Program.cs&lt;/code&gt; and updated all of their documentation to that effect. We're not ready yet in Umbraco to do the same, so there will be some confusion for a while. &lt;/p&gt;

&lt;p&gt;So for now, in Umbraco you're encouraged never to touch &lt;code&gt;Program.cs&lt;/code&gt; and only ever make changes to &lt;code&gt;Startup.cs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mime types
&lt;/h2&gt;

&lt;p&gt;As it turns out, Dean's original question was about trying to figure out how to host and serve the new(-ish) &lt;code&gt;avif&lt;/code&gt; format, &lt;a href="https://jakearchibald.com/2020/avif-has-landed/"&gt;an image format with really great compression&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BLC6WvWT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0k41ors60dxbizytnjh6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BLC6WvWT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0k41ors60dxbizytnjh6.jpg" alt="An image showing the difference in image quality between jpg and avif formats" width="880" height="458"&gt;&lt;/a&gt; &lt;small&gt;Image credit: &lt;a href="https://jakearchibald.com/2020/avif-has-landed/"&gt;Jake Archibald&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Since &lt;a href="https://github.com/dotnet/aspnetcore/issues/39984"&gt;.net doesn't yet support avif&lt;/a&gt; and neither does &lt;a href="https://github.com/SixLabors/ImageSharp/discussions/1320"&gt;ImageSharp&lt;/a&gt;, files of this type just didn't have a MIME type available and trying to use them would result in a 404 error.&lt;/p&gt;

&lt;p&gt;I remembered seeing an example recently to help &lt;a href="https://our.umbraco.com/documentation/Tutorials/Cache-Static-Assets/"&gt;cache static assets&lt;/a&gt;. These are specifically listing file types that ImageSharp doesn't touch. If you added something like &lt;code&gt;jpg&lt;/code&gt; in that list, the request would be cached very early and never hit the ImageSharp middleware. &lt;/p&gt;

&lt;p&gt;Adding/changing MIME types is pretty similar to adding cache control headers, so I played with that code to see how far we could get. &lt;/p&gt;

&lt;p&gt;As it turns out, I was trying my code on &lt;code&gt;webp&lt;/code&gt; files first, changing their MIME type from &lt;code&gt;image/webp&lt;/code&gt; to &lt;code&gt;image/text&lt;/code&gt; to see if it would work. It didn't! I was close though and with &lt;a href="https://twitter.com/rgmbarendse"&gt;some great help from my colleague Ronald&lt;/a&gt; I understood that I was only changing the MIME type for .net and then the next thing kicked in: ImageSharp. When ImageSharp sees a request for a &lt;code&gt;webp&lt;/code&gt; file, it reads the metadata from the file and sets the MIME type (correctly!) back to &lt;code&gt;image/webp&lt;/code&gt;. So after a bit of experimenting we came up with the following code:&lt;/p&gt;

&lt;p&gt;⚡⚠️ &lt;strong&gt;danger danger, this messes with the default webp content type, don't use in production&lt;/strong&gt;⚡⚠️&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http.Extensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.StaticFiles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SixLabors.ImageSharp.Web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SixLabors.ImageSharp.Web.Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Umbraco.Cms.Core.Composing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentTypeComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileExtensionContentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mappings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;".webp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"image/text"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StaticFileOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTypeProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImageSharpMiddlewareOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prepareResponseAsync&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnPrepareResponseAsync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnPrepareResponseAsync&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;formatUtilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FormatUtilities&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatUtilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetExtensionFromUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDisplayUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                    &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;prepareResponseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, that worked!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AChxalrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n8gxsjukpncu73766a8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AChxalrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n8gxsjukpncu73766a8v.png" alt="Screenshot of the Edge browser's network tab showing that the MIME type has updated" width="609" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One caveat though, as you can see in the code above, this is updating the Mappings for an existing content type, and as we learned earlier, &lt;code&gt;avif&lt;/code&gt; is not supported in .net yet and therefore there is no existing content type. We also don't have to think about ImageSharp since it doesn't support &lt;code&gt;avif&lt;/code&gt; yet either.&lt;/p&gt;

&lt;p&gt;So to make &lt;code&gt;avif&lt;/code&gt; get a proper MIME type, the code needed to change a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.StaticFiles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Umbraco.Cms.Core.Composing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentTypeComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileExtensionContentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mappings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".avif"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mappings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".avif"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"image/avif"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StaticFileOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTypeProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentTypeProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we added the MIME type if the mapping doesn't yet exist, if it ever starts to exist in the future we assume it gets the correct mapping anyway. &lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about headers and CORS, my colleague Warren has &lt;a href="https://www.youtube.com/watch?v=Ju_BgmgKvSY"&gt;a great Hack Make Do video on this topic&lt;/a&gt; as well! &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Ju_BgmgKvSY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And there we have it, now we can add a MIME type to any unknown file format and we learned at the same time how to override MIME types for ImageSharp. Plus.. now that we know how to hook into the ImageSharp middleware options, we could probably think of more fun things to do in there as well.&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Debugging your custom website against the Umbraco source code</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Fri, 30 Sep 2022 07:24:17 +0000</pubDate>
      <link>https://dev.to/cultiv/debugging-your-custom-website-against-the-umbraco-source-code-2gg6</link>
      <guid>https://dev.to/cultiv/debugging-your-custom-website-against-the-umbraco-source-code-2gg6</guid>
      <description>&lt;p&gt;Imagine this scenario: you're weeks into building a new website in Umbrco and encounter a bug. You step into Umbraco's source code (available through &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink"&gt;SourceLink&lt;/a&gt;) and you learn what's going on. Even better you spot the problem and you have a good idea of how to fix it! &lt;/p&gt;

&lt;p&gt;Now in the "olden days" (also known as the .NET Framework days) you would be able to do something like: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone the Umbraco source code&lt;/li&gt;
&lt;li&gt;Find the problematic class and attempt a fix&lt;/li&gt;
&lt;li&gt;Build Umbraco&lt;/li&gt;
&lt;li&gt;Copy the dll and pdb files from that build output to your custom website&lt;/li&gt;
&lt;li&gt;Wait for the site to recycle and observe if the bug is fixed&lt;/li&gt;
&lt;li&gt;Or you could go back to the Visual Studio (VS) / Rider instance where the Umbraco source code is open and attach to the IIS / IISExpress that's running your custom website, set a few breakpoints and use the debugger to get closer to the problem you're experiencing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow used to look a bit like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/tF7MIYuYaoU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I've personally been doing this for very rapid debugging for years and it has served me very well in Umbraco versions 8 and below, also known as &lt;a href="https://twitter.com/marcemarc/status/1542487256241430528"&gt;"Classic Umbraco" (thanks Marc Goodson!)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The big caveat here was that if you were to change your custom code in your custom website and do a build in VS/Rider, NuGet would make sure that those dlls that were built straight from the Umbraco source code (including your bug fix) will be overwritten with the officially released dlls for that Umbraco version. So after you build your custom site, you'd have to copy those back in to test your bugfix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Umbraco 9+
&lt;/h2&gt;

&lt;p&gt;Enter Umbraco versions 9 and above, or &lt;a href="https://twitter.com/marcemarc/status/1542487256241430528"&gt;"Modern Umbraco"&lt;/a&gt; all tranformed to run on a new runtime, dotnet version 5/6/etc (the artist previously know as dotnet core). &lt;/p&gt;

&lt;p&gt;The workflow used for .NET Framework does not and can not work any more. When a website is running, there's no way to overwrite dll files new ones, everything is locked. Even if we were to succeed, there's no automatic recyle of the website, so the site will probably just crash.&lt;/p&gt;

&lt;p&gt;There is such a thing as "hot reload", which allows use to make code changes and, without building the solution, continuing to use our website. However, I believe that still actually does a build in the background and I don't know what voodoo is does to load the new code, but I doubt we're able to get anywhere near hooking into it. Hot reload is also very limited, it's pretty easy to make a change that's not supported by hot reload and you have to stop and start your site again anyway.&lt;/p&gt;

&lt;p&gt;What also would not work was copying in the dll / pdb files when the website was not running. As with "Classic Umbraco", once you run the website it will build and copy the dll files from the official NuGet package back into the site and the code we update would be gone.&lt;/p&gt;

&lt;p&gt;We explored different ways of debugging and fixing the Umbraco source code against a custom website in &lt;a href="https://youtu.be/8TKIBVjYN9s?t=506"&gt;a recent UmbraCollab episode&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;UmbraCollab is a weekly event where we find an interesting Umbraco related topic to work on and we try to see how far we can take it in 1 hour, with the help of anyone who wishes to join live. We run UmbraCollab in the voice/video channel on &lt;a href="https://discord.gg/umbraco"&gt;our Discord instance&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We eventually found a nice way of doing a similar type of rapid prototyping and debugging on "Modern Umbraco", with a quite pleasant way to set it up and run a debugger against our custom site.&lt;/p&gt;

&lt;p&gt;As a bit of background, the Umbraco source code is a solution that contains many (VS/Rider)projects, and one of those projects is named &lt;code&gt;Umbraco.Web.UI&lt;/code&gt;. This is really "just" a blank Umbraco website, like you would start building if you were to &lt;a href="https://our.umbraco.com/documentation/Fundamentals/Setup/Install/install-umbraco-with-templates"&gt;set up your own &lt;code&gt;dotnet new Umbraco&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We found that we could add our custom website project as part of the Umbraco source solution. I know, right? 🤯&lt;/p&gt;

&lt;p&gt;In VS/Rider, right-click on the solution and choose to add a project, you can choose an existing project (csproj file) that can live anywhere on your local machine, so in my example I choose &lt;code&gt;D:\Temp\SourceDebug\MyProject\MyProject.cs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LtU5CzkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2hz6p0uzr8o8n572whr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LtU5CzkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2hz6p0uzr8o8n572whr.gif" alt="Image description" width="880" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should be able to run the MyProject project in the dropdown in Visual Studio:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JLwRRzRV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phu9j7g9qtnbtk92hy4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JLwRRzRV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phu9j7g9qtnbtk92hy4z.png" alt="Showing dropdown for 'MyProject' in Visual Studio" width="552" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or in Rider: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WNZPQzNL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54iipasvy1pfb0by8s29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WNZPQzNL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54iipasvy1pfb0by8s29.png" alt="Showing dropdown for 'MyProject' in Rider" width="487" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Magically" from here on in you can alter the Umbraco source code and debug the code, setting breakpoints where you need them. No more copying around dls and pdbs either.&lt;/p&gt;

&lt;p&gt;The modern workflow looks a bit like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/A_sosakA2P4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Pretty smooth huh? 😎&lt;/p&gt;

&lt;p&gt;There were two problems we encountered but they don't always seem to happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You might have a bit of a clash with ModelsBuilder models if you use &lt;code&gt;InMemoryAuto&lt;/code&gt;, so find the folder in &lt;code&gt;UmbracoProject &amp;gt; umbraco &amp;gt; DATA &amp;gt; TEMP &amp;gt; InMemoryAuto&lt;/code&gt; and right-click on it to choose "Exclude" - this way it won't be part of the build&lt;/li&gt;
&lt;li&gt;You might have to add two namespaces to your MyProject's &lt;code&gt;Startup.cs&lt;/code&gt;: &lt;code&gt;using Umbraco.Cms.Core.DependencyInjection;&lt;/code&gt; and &lt;code&gt;using Umbraco.Extensions;&lt;/code&gt; (because: for some reason, when included in the Umbraco source solution, the &lt;code&gt;&amp;lt;ImplicitUsings&amp;gt;enable&amp;lt;/ImplicitUsings&amp;gt;&lt;/code&gt; directive in &lt;code&gt;MyProject.csproj&lt;/code&gt; is not always recognized).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;There's two caveats to this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your IDE (Visual Studio, Rider, etc.) will update the &lt;code&gt;umbraco.sln&lt;/code&gt; file with the reference to your custom project. If you're planning to contribute your updated code back to Umbraco using a pull request, make sure to revert the changes to &lt;code&gt;umbraco.sln&lt;/code&gt; before you commit and push, then the team of reviewers doesn't have to remind you.&lt;/li&gt;
&lt;li&gt;Likewise, your custom project's &lt;code&gt;csproj&lt;/code&gt; file will now include a reference to the directory where you cloned Umbraco-CMS and if you had to exclude ModelsBuilder files, that will also have been added to the &lt;code&gt;csproj&lt;/code&gt; file. If you want to keep your colleauges happy, don't commit those changes to source control either.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I always scan whatever changes I'm adding to source control (don't be the &lt;code&gt;git add .&lt;/code&gt; person please, take some pride in what you commit!) so it's easy to revert these changes before committing.&lt;/p&gt;

&lt;p&gt;Let me know if you have even more clever ways of doing this in "Modern Umbraco", I'm personally very much enjoying this new approach and it's making me nice and productive!&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Umbraco Discord wrap-up week 18</title>
      <dc:creator>Sebastiaan Janssen</dc:creator>
      <pubDate>Fri, 06 May 2022 07:59:03 +0000</pubDate>
      <link>https://dev.to/cultiv/umbraco-discord-wrap-up-week-18-c2j</link>
      <guid>https://dev.to/cultiv/umbraco-discord-wrap-up-week-18-c2j</guid>
      <description>&lt;p&gt;Every week I realize what a wealth of information gets shared in the &lt;a href="https://discord.gg/umbraco"&gt;Umbraco Discord&lt;/a&gt; and the many things I might want to look up again later, so I've started keeping a list!&lt;/p&gt;

&lt;h2&gt;
  
  
  CORS
&lt;/h2&gt;

&lt;p&gt;How to modify CORS on Umbraco netcore, asks &lt;a href="https://twitter.com/SeanThorne"&gt;Sean&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zGbErIZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pphr7sktkyhsusausrn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zGbErIZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pphr7sktkyhsusausrn.png" alt="Image description" width="332" height="66"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;My teammate &lt;a href="https://github.com/p-m-j"&gt;Paul&lt;/a&gt; has the answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UmbracoPipelineOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UmbracoPipelineFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cors"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PostPipeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cors&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// for example&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// for example&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyOrigin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// for example&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Should just be able to do this in configure services (or a composer etc) &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Modelsbuilder
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/hfloyd"&gt;Heather&lt;/a&gt; made me check out &lt;a href="https://github.com/limbo-works/Limbo.Umbraco.ModelsBuilder"&gt;Limbo Modelsbuilder&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v4xmy3hE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goae28u2eiy55iidvgph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v4xmy3hE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goae28u2eiy55iidvgph.png" alt="Image description" width="852" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While, by the way, Sean is plugging his upcoming &lt;a href="https://codegarden.umbraco.com/"&gt;Codegarden&lt;/a&gt; talk about building the &lt;a href="https://aardman.com/"&gt;Aardman&lt;/a&gt; website on Umbraco.&lt;/p&gt;

&lt;p&gt;Heather notes that much of the Modelsbuilder functionality that was available in the v8 Visual Studio extension is now again available in this package.&lt;/p&gt;
&lt;h2&gt;
  
  
  Element types and tags
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/skttl"&gt;Søren&lt;/a&gt; blogged about &lt;a href="https://dev.to/skttl/bypassing-property-editor-restrictions-in-umbraco-element-types-21ko"&gt;bypassing (some) of our limitations&lt;/a&gt; when trying to use certain property editors in nested content / block editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sejkzZKy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsumqppwd8arg5n3f6m3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sejkzZKy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsumqppwd8arg5n3f6m3.png" alt="Image description" width="686" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://twitter.com/leekelleher"&gt;Lee&lt;/a&gt; also points out that his wonderful &lt;a href="https://github.com/leekelleher/umbraco-contentment"&gt;Contentment&lt;/a&gt; package can use the data from property editors like the Tags editors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RRzlapQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4caked7fmak483erqnd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RRzlapQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4caked7fmak483erqnd4.png" alt="Image description" width="19" height="8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lee also noted during UmbraCollab that Contentment already works with Umbraco 10, he has an &lt;a href="https://github.com/leekelleher/umbraco-contentment/releases/tag/4.0.0-alpha001"&gt;alpha release&lt;/a&gt; out now! &lt;/p&gt;
&lt;h2&gt;
  
  
  UmbraCollab
&lt;/h2&gt;

&lt;p&gt;As you may or may not know, every Thursday at 13:00 CET we get together on &lt;a href="https://discord.gg/umbraco"&gt;Discord&lt;/a&gt; and do a bit of mob programming. This week we tried to do some Umbraco 10 upgrades. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/codesharepaul"&gt;Paul&lt;/a&gt; kicked off with what was going to be an attempt to upgrade his &lt;a href="https://our.umbraco.com/packages/starter-kits/portfolio-starter-kit/"&gt;Portfolio Starter Kit&lt;/a&gt; and &lt;a href="https://our.umbraco.com/packages/backoffice-extensions/ourumbracoexaminecontentapp/"&gt;Examine Content App&lt;/a&gt; to Umbraco 10. Turns out both of those "just" worked on v10 already, so there was really nothing to do, excellent!&lt;/p&gt;

&lt;p&gt;Then I tried to upgrade my blog to v10 and ran into some problems, similar problems happened on a different test site I had laying around and it turns out I forgot to upgrade the sites to .NET 6 - after which everything just worked, smooth. Thanks &lt;a href="https://twitter.com/bjarkeberg"&gt;Bjarke&lt;/a&gt; for the hint and for providing &lt;a href="https://our.umbraco.com/documentation/Fundamentals/Setup/Upgrading/version-specific#version-9-to-version-10"&gt;the updated docs&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The recording is on YouTube: &lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://www.youtube.com/watch?t=544&amp;amp;v=Q-RB0Jyq9jc&amp;amp;feature=youtu.be" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--B2NUp846--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.ytimg.com/vi/Q-RB0Jyq9jc/maxresdefault.jpg%3Fsqp%3D-oaymwEmCIAKENAF8quKqQMa8AEB-AH-CYAC0AWKAgwIABABGC4gVyh_MA8%3D%26rs%3DAOn4CLAfeNtRZK5Z9HNhlAoWocvafSU7VQ" height="495" class="m-0" width="880"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://www.youtube.com/watch?t=544&amp;amp;v=Q-RB0Jyq9jc&amp;amp;feature=youtu.be" rel="noopener noreferrer" class="c-link"&gt;
          UmbraCollab - 5th May 2022 - YouTube
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          The Umbraco community come together on Discord to hack and collobrate on a problem on their lunch time every Thursday 12 - 1PM GMT UK
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--o3ruNiUH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.youtube.com/s/desktop/ff71ea81/img/favicon.ico" width="16" height="16"&gt;
        youtube.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/garpunkal"&gt;Gareth&lt;/a&gt; wrote a &lt;a href="https://dev.to/garpunkal/umbraco-9-azure-cache-for-redis-3ipi"&gt;blog post about caching Umbraco content in Redis&lt;/a&gt; after getting some feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aCbfXEq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnfmmujxb6behs8vjlhf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aCbfXEq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnfmmujxb6behs8vjlhf.png" alt="Image description" width="852" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting properties to read-only
&lt;/h2&gt;

&lt;p&gt;Harry asked about making some properties read-only under certain conditions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kS9F7d1S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ji33cajnwpmarjq2qurc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kS9F7d1S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ji33cajnwpmarjq2qurc.png" alt="Image description" width="832" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And Paul Seal reminds us of &lt;a href="https://twitter.com/marcemarc/"&gt;Marc Goodson&lt;/a&gt;'s blogpost about his attempts at &lt;a href="http://tooorangey.co.uk/posts/umbraco-v8-variants-and-limiting-editor-access-by-language-an-adventure-story/"&gt;limiting editor access by language&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Vite
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dan-hammond"&gt;Dan Hammond&lt;/a&gt; has been on a journey this week to get Vite to load in the backoffice and eventually seems to have gotten there with a bit of help from &lt;a href="https://twitter.com/nathanwoulfe"&gt;Nathan&lt;/a&gt; and by orchestrating a  big hack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lRf_z8S7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyu13gj7tptbsssox99p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lRf_z8S7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyu13gj7tptbsssox99p.png" alt="Image description" width="833" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He notes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the loader:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;queued&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queued&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queued&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/backoffice/src/main.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In my Vite script:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableReady&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableReady&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Visual Studio code cleanup
&lt;/h2&gt;

&lt;p&gt;Lastly, Matt Wise shared a useful link for those not yet using Rider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9R0EKqGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpmnvn5x7nesxsm3io9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9R0EKqGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpmnvn5x7nesxsm3io9u.png" alt="Image description" width="794" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/visualstudio/bringing-code-cleanup-on-save-to-visual-studio-2022-17-1-preview-2/"&gt;Automatic code cleanup&lt;/a&gt; on saving a file is coming to Visual Studio at last.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap!
&lt;/h2&gt;

&lt;p&gt;Wow, that was a lot going on in just a single week! Hard to keep up.&lt;/p&gt;

&lt;p&gt;As always, &lt;a href="https://discord.gg/umbraco"&gt;you're welcome to join us and collaborate&lt;/a&gt; on anything you're working on in Umbraco at the moment. Or just lurk, as you can see there's a lot to learn from.&lt;/p&gt;

&lt;h3&gt;
  
  
  UmbraCollab opportunity 💡
&lt;/h3&gt;

&lt;p&gt;As a final note:&lt;/p&gt;

&lt;p&gt;We are always looking for people to join us on UmbraCollab. Thursday at 13:00, strictly limited to one hours of collaborating on something. Topics can include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;working on a pull request&lt;/li&gt;
&lt;li&gt;working on a new package&lt;/li&gt;
&lt;li&gt;converting existing packages to a newer version&lt;/li&gt;
&lt;li&gt;trying out new (or old) features in Umbraco&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You drive the screen and we help you out with helpful (and sometimes completely useless) comments. Usually we get pretty far in an hour and most of all we all learn something new. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/cultiv"&gt;Tweet me, DMs are open 👋&lt;/a&gt;, if you would like to take the opportunity and share something interesting!&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
  </channel>
</rss>
