<?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>Forem: Jason Elkin</title>
    <description>The latest articles on Forem by Jason Elkin (@jasonelkin).</description>
    <link>https://forem.com/jasonelkin</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%2F262044%2Fb08892d4-e0ca-4b19-a115-e29de1c755ef.jpg</url>
      <title>Forem: Jason Elkin</title>
      <link>https://forem.com/jasonelkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jasonelkin"/>
    <language>en</language>
    <item>
      <title>Mitigating CVE-2025-67288 in Umbraco (if you feel you need to)</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Wed, 04 Feb 2026 02:49:57 +0000</pubDate>
      <link>https://forem.com/jasonelkin/mitigating-cve-2025-67288-in-umbraco-13-if-you-feel-you-need-to-bl3</link>
      <guid>https://forem.com/jasonelkin/mitigating-cve-2025-67288-in-umbraco-13-if-you-feel-you-need-to-bl3</guid>
      <description>&lt;p&gt;Firstly, this is a really ill-informed CVE. If you've not read &lt;a href="https://forum.umbraco.com/t/cve-2025-67288-and-v13/7079/6?u=jasonelkin" rel="noopener noreferrer"&gt;my comment on the Umbraco Forum&lt;/a&gt;, I'll repeat the key bits, otherwise feel free to scroll down to the good stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bit about CVE-2025-67288
&lt;/h2&gt;

&lt;p&gt;If you want the details you can read the full CVE at &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-67288" rel="noopener noreferrer"&gt;nvd.nist.gov&lt;/a&gt;. Umbraco is in the process of disputing it.&lt;/p&gt;

&lt;p&gt;Let me break down why I think it's mostly nonsense...&lt;/p&gt;

&lt;h3&gt;
  
  
  Remote Code Execution?
&lt;/h3&gt;

&lt;p&gt;It’s an attention grabbing PoC..&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload a crafted PDF file containing embedded JavaScript.&lt;/li&gt;
&lt;li&gt;Observe that JavaScript from the PDF executes in the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what does Umbraco have to do with that? You could upload the file anywhere - in fact, and click this link if you dare, &lt;a href="https://jasonelkin.github.io/docs/pdfs/success_xss.pdf" rel="noopener noreferrer"&gt;I've uploaded that file to a GitHub pages site&lt;/a&gt;.&lt;br&gt;
Does that mean GitHub is vulnerable to a CWE-434…? No, and neither is Umbraco.&lt;/p&gt;

&lt;p&gt;This is what a &lt;a href="https://cwe.mitre.org/data/definitions/434.html" rel="noopener noreferrer"&gt;CWE-434&lt;/a&gt; means...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The product allows the upload or transfer of dangerous file types that are &lt;strong&gt;automatically processed within its environment&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Emphasis mine, but this "CVE" doesn’t pass that test. Umbraco doesn't process the PDF such that arbitrary code can be run on your Umbraco site.&lt;/p&gt;

&lt;p&gt;So, remote code execution worthy a CVSS score of 10.0? No.&lt;/p&gt;
&lt;h3&gt;
  
  
  Is is really XSS?
&lt;/h3&gt;

&lt;p&gt;Look more closely at the XSS (Cross Site Scripting) claim. Where does the JavaScript actually run? In the browser, sure, but it’s not run in the context of the Umbraco site (or GitHub.io, if you were brave and clicked the link above). It’s sandboxed by the browser and not treated like it’s running on your domain at all.&lt;/p&gt;

&lt;p&gt;This is such a common misconception that Chromium has an FAQ titled "Does executing JavaScript in a PDF file mean there's an XSS vulnerability?". Spoiler alert, &lt;a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md#Does-executing-JavaScript-in-a-PDF-file-mean-there_s-an-XSS-vulnerability" rel="noopener noreferrer"&gt;the answer is no&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  It's JavaScript, but not as we know it
&lt;/h3&gt;

&lt;p&gt;It doesn’t have access to any important browser APIs - i.e. no fetch() or document.cookies etc. in fact if you look at the alert itself you’ll see it’s calling app.alert()… that’s not a web browser API but a PDF one :face_with_monocle:&lt;/p&gt;

&lt;p&gt;If you try and craft a more complex JavaScript payload you’ll quickly find that the browser will block any APIs that could present a real risk to the user.&lt;/p&gt;

&lt;p&gt;Even if you could craft malicious JavaScript that executes in a browser, that CVE would really be the browser’s problem, not Umbraco’s.&lt;/p&gt;

&lt;p&gt;If you're feeling extra nerdy, you can &lt;a href="https://docs.google.com/document/d/1olIb-1IFVqP2fLTq0eAdW-aL-K2dDDZGDe5mZgHGfO8/edit?tab=t.0#heading=h.wwlum47z8pg1" rel="noopener noreferrer"&gt;have a read of the design doc for the PDF Viewer in Chromium&lt;/a&gt;. It talks about sandboxing, what kinds of JavaScript execution are allowed, and why.&lt;/p&gt;

&lt;p&gt;Obviously there’s still a potential social engineering/phishing angle with these crafted PDFs… but that’s a whole different issue.&lt;/p&gt;

&lt;p&gt;But good news, you can still mitigate against it!&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter the IFileStreamSecurityAnalyzer
&lt;/h2&gt;

&lt;p&gt;It's a simple interface you can implement that Umbraco will then run uploaded filestreams through so you can add any logic you want to check if the files are safe.&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;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IFileStreamSecurityAnalyzer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Indicates whether the analyzer should process the file&lt;/span&gt;
    &lt;span class="c1"&gt;/// The implementation should be considerably faster than IsConsideredSafe&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="fileStream"&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ShouldHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Analyzes whether the file content is considered safe&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="fileStream"&amp;gt;Needs to be a Read/Write seekable stream&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;Whether the file is considered safe&amp;lt;/returns&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsConsideredSafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;fileStream&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 docs &lt;a href="https://docs.umbraco.com/umbraco-cms/reference/security/serverside-file-validation" rel="noopener noreferrer"&gt;have a nice example&lt;/a&gt; showing you how to protect against a potentially malicious SVG file.&lt;/p&gt;

&lt;p&gt;In a similar fashion we can implement our own class that will read a PDF, look for any JavaScript, and return false when &lt;code&gt;IsConsideredSafe()&lt;/code&gt; is called.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;ShouldHandle()&lt;/code&gt; method only takes the stream. File extensions don't necessarily match up with what's actually in a file so we need to check the contents first to see if this is actually a PDF. A quick way of doing that is to parse just the file signature in the file's header.&lt;/p&gt;

&lt;p&gt;There's a &lt;a href="https://github.com/neilharvey/FileSignatures/" rel="noopener noreferrer"&gt;package for that&lt;/a&gt;!&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 FileSignatures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for the PDF reading, I'm using &lt;a href="https://uglytoad.github.io/PdfPig/" rel="noopener noreferrer"&gt;PdfPig&lt;/a&gt;:&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 PdfPig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I need to add a disclaimer. The following code is based on something I'm using in production but the PDF library I'm using has rather restrictive/expensive licensing and I wanted to give you a demo that "Just Works" with some friendly OSS libraries - so I asked AI to help me rewrite this for PdfPig.&lt;/p&gt;

&lt;p&gt;Basically, it just looks for certain types of object in the document and, returns false if it finds them.&lt;/p&gt;

&lt;p&gt;Why so complex? This is partly down to the library we're using here and partly down to how PDFs work. You can stuff pretty much anything in a PDF and the structure doesn't have to be as predictably formed as, say, an XML file. Different PDF libraries parse the document differently into their own DOM too. Here we have to iterate over everything in the PDF to check if it's the kind of object were looking for. Other PDF libraries keep track of every element in a separate dictionary that you can use to do a quicker lookup on.&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;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PdfSecurityAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PdfSecurityAnalyzer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IFileFormatInspector&lt;/span&gt; &lt;span class="n"&gt;fileFormatInspector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFileStreamSecurityAnalyzer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HashSet&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="n"&gt;_suspiciousKeys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"AA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Additional Actions&lt;/span&gt;
        &lt;span class="s"&gt;"OpenAction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Actions triggered on document open&lt;/span&gt;
        &lt;span class="s"&gt;"JS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// JavaScript Stream&lt;/span&gt;
        &lt;span class="s"&gt;"JavaScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Explicit JavaScript name&lt;/span&gt;
        &lt;span class="s"&gt;"Launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Launch external applications&lt;/span&gt;
        &lt;span class="s"&gt;"SubmitForm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Form submission (potential data exfiltration)&lt;/span&gt;
        &lt;span class="s"&gt;"ImportData"&lt;/span&gt;    &lt;span class="c1"&gt;// Import external data&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;bool&lt;/span&gt; &lt;span class="nf"&gt;IsConsideredSafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;options&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;ParsingOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;UseLenientParsing&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="k"&gt;try&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;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileStream&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visited&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;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IndirectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Check the entire document structure starting from catalog&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CatalogDictionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&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="k"&gt;false&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="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to parse PDF for security analysis"&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;false&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;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IndirectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visited&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="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DictionaryToken&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CheckDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ArrayToken&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CheckArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;IndirectReferenceToken&lt;/span&gt; &lt;span class="n"&gt;refToken&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CheckIndirectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;StreamToken&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamDictionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ObjectToken&lt;/span&gt; &lt;span class="n"&gt;objToken&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CheckDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DictionaryToken&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IndirectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check for suspicious keys&lt;/span&gt;
        &lt;span class="k"&gt;foreach&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;keyName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&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;_suspiciousKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyName&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;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Subtype Check: /S /JavaScript&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;keyName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"S"&lt;/span&gt; 
                &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NameToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;NameToken&lt;/span&gt; &lt;span class="n"&gt;subtype&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;subtype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"JavaScript"&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="k"&gt;true&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="c1"&gt;// Recurse into all dictionary values&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&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;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&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="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CheckArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ArrayToken&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IndirectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visited&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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CheckIndirectReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IndirectReferenceToken&lt;/span&gt; &lt;span class="n"&gt;refToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IndirectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;)&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;visited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;visited&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="n"&gt;refToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&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;actualToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ContainsSuspiciousContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&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;false&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="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ShouldHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;fileStream&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;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fileFormatInspector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DetermineFileFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileStream&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;format&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Pdf&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="k"&gt;true&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="k"&gt;false&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;And then to register it, use an IComposer:&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegisterFileStreamSecurityAnalysers&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;recognised&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FileFormatLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFormats&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;OfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pdf&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inspector&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;FileFormatInspector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recognised&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;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFileFormatInspector&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;inspector&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;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFileStreamSecurityAnalyzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfSecurityAnalyzer&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Notice I'm configuring the &lt;code&gt;FileFormatInspector&lt;/code&gt; to only look for PDFs, which is better for performance but you can add multiple file types or configure it to look for all types known by the library - have a look at &lt;a href="https://github.com/neilharvey/FileSignatures/" rel="noopener noreferrer"&gt;the GitHub Repo&lt;/a&gt; for more info.&lt;/p&gt;

&lt;p&gt;With the above code implemented, try and upload a PDF containing JavaScript (like the perfectly benign one I linked to above), and you'll get this lovely error message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyn5khiy0wekqkl8fx7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyn5khiy0wekqkl8fx7b.png" alt=" " width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may get some false positives, but this was all about false positives really, wasn't it...&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>security</category>
    </item>
    <item>
      <title>The Bot That Shouldn’t Have Taken Down My Umbraco Site, and the WAF Rule That Fixed It</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Fri, 21 Nov 2025 11:19:36 +0000</pubDate>
      <link>https://forem.com/jasonelkin/the-bot-that-shouldnt-have-taken-down-my-umbraco-site-and-the-waf-rule-that-fixed-it-36o6</link>
      <guid>https://forem.com/jasonelkin/the-bot-that-shouldnt-have-taken-down-my-umbraco-site-and-the-waf-rule-that-fixed-it-36o6</guid>
      <description>&lt;p&gt;This week, one of our sites was brought down by a Denial of Service attack. It wasn’t really a DoS attack, i.e. I’m sure that wasn’t the attackers’ aim, but the attack did cause a Denial of Service nonetheless.&lt;/p&gt;

&lt;p&gt;It was clear a bot was spamming the site with several thousands of requests per minute, but that in itself was well within the threshold that we’d expect the site to be able to handle - this is a modern Umbraco site with load-balancing/auto-scaling and a well optimized codebase after all.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, &lt;em&gt;what&lt;/em&gt; was going on?
&lt;/h2&gt;

&lt;p&gt;The problem was the nature of the traffic – form submissions. Shedloads of form submissions, being made to both Umbraco forms and our custom surface controllers, that were causing errors.&lt;/p&gt;

&lt;p&gt;It was those errors that contributed to a significant degradation in performance, leaving us with some challenges that we had to find a way to overcome:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Umbraco Forms
&lt;/h3&gt;

&lt;p&gt;Umbraco Forms has a honeypot field built-in – this is a hidden text field that looks tempting to bots, so they fill it in. If a form is submitted with a value in that field, Umbraco Forms will reject the submission. The bot hitting our site was totally falling for the honeypot - great! So what’s the problem? When Umbraco Forms rejects that submission it also logs that rejection.&lt;/p&gt;

&lt;p&gt;Logging is a non-trivial concern in performance-critical applications. In Umbraco, every log entry is a disk write operation by default. So, although the site would normally be able to handle this level of traffic easily, these POST requests are resulting in a disk write (which in an Azure Web App is actually a network request under the hood) – gobbling up IO and slowing the site down.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Surface controllers
&lt;/h3&gt;

&lt;p&gt;We had a bigger problem with our surface controllers. Surface controllers &lt;a href="https://docs.umbraco.com/umbraco-cms/reference/routing/surface-controllers/#protecting-surface-controller-routes" rel="noopener noreferrer"&gt;use a hidden &lt;code&gt;ufprt&lt;/code&gt; field&lt;/a&gt; to store an encrypted value for routing form submissions. If there’s a problem with this field, i.e. Umbraco can’t decrypt it, an error is thrown. &lt;/p&gt;

&lt;p&gt;The bot spamming us was stuffing that field with SQL in the vain hope of carrying out an SQL Injection. Of course Umbraco was unable to decrypt that into a valid &lt;code&gt;ufprt&lt;/code&gt; value so threw an error.&lt;/p&gt;

&lt;p&gt;So, just like with Umbraco Forms we have an error being logged for each of these requests, with the added overhead of an Exception being thrown.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 reCAPTCHA
&lt;/h4&gt;

&lt;p&gt;There was another problem with our surface controllers: reCAPTCHA.&lt;/p&gt;

&lt;p&gt;Like a lot of other .NET projects, we use an attribute on our controllers to handle reCAPTCHA validation. The problem is that this validation was running before Umbraco tried to decrypt the ufprt field – so as well as the exception and extra writes to disk, every request was also making an HTTP request to the reCAPTCHA API, gobbling up precious TCP threads.&lt;/p&gt;

&lt;h3&gt;
  
  
  We're serving all these requests!
&lt;/h3&gt;

&lt;p&gt;On top of the errors themselves, we’re then returning a 500 error page for most of these requests.&lt;/p&gt;

&lt;p&gt;These are dynamic/server-rendered pages on this site so they are using compute – it’s not a big deal, but we don’t want to be wasting those resources on bot requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;p&gt;Sure, we could look at each of the individual problems above and solve for them but…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I still want to know about errors, so I need logging.&lt;/li&gt;
&lt;li&gt;I still want to use reCAPTCHA to validate form submissions, especially from bots.&lt;/li&gt;
&lt;li&gt;I still want to serve dynamic error pages (though there’s an argument to be made for better caching).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For legitimate traffic, I still want all of this to happen. For illegitimate traffic I don’t want our app to be responding at all - it’s a waste of resources. In fact, W3C’s Web Sustainability Guidelines recommend &lt;a href="https://w3c.github.io/sustainableweb-wsg/#use-automation-wisely" rel="noopener noreferrer"&gt;filtering suspicious activity&lt;/a&gt; for this reason.&lt;/p&gt;

&lt;p&gt;We should be blocking this traffic at the edge, and that’s a job for the WAF - In this case Cloudflare.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;It’s time for a new WAF rule.&lt;/p&gt;

&lt;p&gt;Fortunately, there’s a really simple rule that we can deploy in Cloudflare to block this traffic.&lt;/p&gt;

&lt;p&gt;I set up a new &lt;a href="https://developers.cloudflare.com/waf/rate-limiting-rules/" rel="noopener noreferrer"&gt;Rate Limiting Rule&lt;/a&gt; with an expression that matches all POST requests, then rate limited it to 20 POST requests per IP address every 20 seconds before issuing a challenge.&lt;/p&gt;

&lt;p&gt;No legitimate user is going to be making more than 20 POST requests in 20 seconds. You might even think that’s a little high, but we can only rate-limit by IP address, and multiple users will legitimately be sharing a single IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here’s what that looks like in the portal:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8wf0rxujbta0fbgztxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8wf0rxujbta0fbgztxa.png" alt="A screenshot of the Cloudflare rate limiting rule UI" width="747" height="1167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;3 million requests have been blocked since I deployed that rule yesterday and, most importantly, the site has remained online and performant for legitimate users throughout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcamqqb4jj6sn0n7m11h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcamqqb4jj6sn0n7m11h.png" alt="A summary from the Cloudflare dashboard showing 3 Million blocked requests in the last 24 hours" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Win.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>waf</category>
      <category>security</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Yet another CORS in Umbraco post</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Thu, 06 Mar 2025 11:05:18 +0000</pubDate>
      <link>https://forem.com/jasonelkin/yet-another-cors-in-umbraco-post-1nkg</link>
      <guid>https://forem.com/jasonelkin/yet-another-cors-in-umbraco-post-1nkg</guid>
      <description>&lt;p&gt;I don't know how many blog, forum, and Discord posts I've read about how to set up CORS for a headless site in Umbraco. It feels like a lot. None of them worked for me 😥&lt;/p&gt;

&lt;p&gt;It's probably just the usual problem of incorrect middleware order, which is always slightly tricky in Umbraco as some of the pipeline configuration is abstracted away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What am I trying to do?
&lt;/h2&gt;

&lt;p&gt;POST a form, via the Fetch API, from the frontend of my Headless site to my Umbraco instance backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;CORS. It's there to keep us all safe, but it can be a pain to configure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ebxznogy9f1yo2dvig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43ebxznogy9f1yo2dvig.png" alt="An error message from a CORS failure" width="800" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately there's a built-in CORS middleware in ASP.NET Core that can be configured &lt;/p&gt;

&lt;h3&gt;
  
  
  The Catch
&lt;/h3&gt;

&lt;p&gt;I can't modify &lt;code&gt;Startup.cs&lt;/code&gt;. Well, I &lt;em&gt;could&lt;/em&gt; but it would ruin my project's architecture. The headless features are added in a separate assembly to the site's Core and Web code. The idea is that we can just drop the headless library into a baseline project to "switch on" headless functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Below is what is now working for me.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://docs.umbraco.com/umbraco-cms/implementation/composing" rel="noopener noreferrer"&gt;composer&lt;/a&gt; lives in my &lt;code&gt;BaseProject.Headless&lt;/code&gt; library and adds the appropriate CORS, so long as a frontend URI is set.&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Composer&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;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FrontendCorsPolicyName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"FrontendCorsPolicy"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryCreate&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="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Headless:FrontendUrl"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UriKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Absolute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;frontendUri&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;AddCors&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="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FrontendCorsPolicyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corsPolicyBuilder&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;corsPolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOrigins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frontendUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetIsOriginAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;)&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="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="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;UmbracoPipelineOptions&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="n"&gt;options&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="n"&gt;FrontendCorsPolicyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;PrePipeline&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="p"&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;FrontendCorsPolicyName&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;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;Of special note is this:&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;corsPolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetIsOriginAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"localhost"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is here because we can't guarantee devs running the frontend locally will be using any specific port (or scheme) - this should probably be wrapped in an environment check.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>dotnet</category>
      <category>aspdotnet</category>
      <category>cors</category>
    </item>
    <item>
      <title>When does != true != !true?</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Wed, 13 Dec 2023 10:56:33 +0000</pubDate>
      <link>https://forem.com/jasonelkin/when-does-true-true-3p39</link>
      <guid>https://forem.com/jasonelkin/when-does-true-true-3p39</guid>
      <description>&lt;p&gt;This is a rather contrived tongue-in-cheek edge case, and super nerdy... but I have some code that does this:&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;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;liar&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="c1"&gt;// True&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;liar&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="c1"&gt;// True&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;liar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  WHY!?!
&lt;/h2&gt;

&lt;p&gt;Partly because I'm a contrarian, but also to highlight something that we might take for granted in C#.&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/leekelleher"&gt;@leekelleher&lt;/a&gt; wrote &lt;a href="https://dev.to/leekelleher/lees-opinions-on-umbraco-coding-standards-4c7n"&gt;a great article about his take on coding standards&lt;/a&gt; where he compares two different ways of doing the same thing, specifically this:&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;if&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;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;//... &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// or&lt;/span&gt;

&lt;span class="k"&gt;if&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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;//... &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lee points out that functionally (and even in IL) these two lines of code are exactly the same (and for &lt;code&gt;string.IsNullOrWhiteSpace()&lt;/code&gt; that will almost certainly always be the case), but they don't &lt;em&gt;have&lt;/em&gt; to be the same...&lt;/p&gt;

&lt;p&gt;Logically these lines of code are doing something different, which shouldn't be a surprise because they &lt;em&gt;are&lt;/em&gt; different.&lt;/p&gt;

&lt;p&gt;If we do away with the method being invoked, which returns a &lt;code&gt;bool&lt;/code&gt;, and the &lt;code&gt;if&lt;/code&gt; then we can focus a bit on the logic.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// x IS NOT a.&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// x IS (a EQUALS false).&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That first line is a logical negation. The second line is an equality comparison against a (static) &lt;code&gt;false&lt;/code&gt; value. These are using different operators, and in C# operators are implemented explicitly and independently and can be overridden.&lt;/p&gt;

&lt;p&gt;What do I mean by that? let's take a look at some code from my (very naughty) Liar struct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jason's Evil Liar Struct
&lt;/h2&gt;

&lt;p&gt;BTW, don't do this 🙈.&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;public&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Liar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTrue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isTrue&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;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;==(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;right&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="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;right&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;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;!=(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;right&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="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&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 I'm defining what should happen when someone compares my Liar struct to a &lt;code&gt;bool&lt;/code&gt;. You can, of course, spot the deliberate mistake.&lt;/p&gt;

&lt;p&gt;The point is, &lt;code&gt;!=&lt;/code&gt; and &lt;code&gt;==&lt;/code&gt; invoke different methods, i.e. &lt;code&gt;!=&lt;/code&gt; is not simply a logical negation of &lt;code&gt;==&lt;/code&gt;. This means that &lt;code&gt;a != b&lt;/code&gt; is not doing the same as &lt;code&gt;!(a == b)&lt;/code&gt; and vice versa.&lt;/p&gt;

&lt;p&gt;So what about the logical negation operator? You'd be forgiven for thinking that &lt;code&gt;a&lt;/code&gt; &lt;em&gt;has&lt;/em&gt; to be a bool for &lt;code&gt;!a&lt;/code&gt; to work, but that's not the case. It could be anything that has the implicit operator for a bool defined (or the true/false operators). That's right JavaScript fans, you can make truthy/falsey types in C# 😬.&lt;/p&gt;

&lt;p&gt;Here's the code for implicit operator that lets me use my Liar struct as if it's a bool:&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;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;liar&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="n"&gt;liar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&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 we can do this:&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;Liar&lt;/span&gt; &lt;span class="n"&gt;liar&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="k"&gt;true&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;liar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&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 can also add an implicit operator so that we don't even need to create an instance of &lt;code&gt;Liar&lt;/code&gt;, and can just assign true to it:&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;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="nf"&gt;Liar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTrue&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="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isTrue&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 we can do this:&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;Liar&lt;/span&gt; &lt;span class="n"&gt;liar&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Yeah, but this never happens in the &lt;em&gt;real world&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Unless someone does something silly... and people &lt;em&gt;never&lt;/em&gt; do silly things... &lt;/p&gt;

&lt;p&gt;Though that's not really my point, there are a few other things I'd like you to take away from this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Code that looks like it's doing something different, &lt;em&gt;is&lt;/em&gt; doing something different
&lt;/h3&gt;

&lt;p&gt;Two lines of code that look like they're doing something different almost always &lt;em&gt;are&lt;/em&gt; doing something different, even if the outcome is functionally the same.&lt;/p&gt;

&lt;p&gt;Sure, in the real world the difference between  &lt;code&gt;!string.IsNullOrWhiteSpace(input)&lt;/code&gt; and &lt;code&gt;string.IsNullOrWhiteSpace(input) == false&lt;/code&gt; is largely academic, so  you can choose what works for you. &lt;strong&gt;But&lt;/strong&gt; that's not necessarily going to be the case all the time, and being aware of the difference here might just save you a few hours of debugging one day.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. In C# &lt;em&gt;we&lt;/em&gt; can choose how operators work
&lt;/h3&gt;

&lt;p&gt;At least with our own code, it's called operator overloading. &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've known some C# devs get confused by equality in JavaScript with &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;===&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;,&lt;code&gt;!==&lt;/code&gt; and truthy/falsely logic like &lt;code&gt;!someString&lt;/code&gt;. In many ways this is actually a lot simpler than C# because it is what it is, and we can't change it. &lt;/p&gt;

&lt;p&gt;In C# all bets are off - you have complete control in defining how operators work with the classes/structs etc. that you define.&lt;/p&gt;

&lt;p&gt;This is actually a super powerful feature of C# and I love it when library authors implement it to improve the developer experience, like this 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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ratio&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="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;9&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;ratio&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"16:9"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;//😙👌&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. You can write operators to compare pretty much anything!
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;Liar&lt;/code&gt; is just a wrapped &lt;code&gt;bool&lt;/code&gt; to make a silly example, but we can have any logic inside these operators and use them for more complicated types. &lt;/p&gt;

&lt;p&gt;There's also no limit on what types you can add operators for, here I've just added code to compare &lt;code&gt;Liar&lt;/code&gt; with &lt;code&gt;bool&lt;/code&gt; but we can write code to compare our types to anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for nerding out with me...
&lt;/h2&gt;

&lt;p&gt;If you've not had a go at overloading operators in your own types before, why not give it a try?&lt;/p&gt;

&lt;p&gt;I'm sure you've got classes that would be useful if they could be compared against a &lt;code&gt;string&lt;/code&gt; value, like the ratio example above, or an &lt;code&gt;int&lt;/code&gt; or whatever.&lt;/p&gt;




&lt;p&gt;Here's the full example &lt;code&gt;Liar struct&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;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Liar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTrue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEquatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isTrue&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;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;==(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;right&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="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;right&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;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="p"&gt;!=(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;right&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="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&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;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;liar&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="n"&gt;liar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&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;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="nf"&gt;Liar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTrue&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="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isTrue&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;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;obj&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="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Liar&lt;/span&gt; &lt;span class="n"&gt;liar&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;liar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;_true&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;override&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&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="n"&gt;_true&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&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;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;other&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="n"&gt;_true&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&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;



</description>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Why I built an Umbraco package that does "nothing"</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Tue, 28 Jun 2022 06:52:56 +0000</pubDate>
      <link>https://forem.com/jasonelkin/why-i-built-an-umbraco-package-that-does-nothing-27n1</link>
      <guid>https://forem.com/jasonelkin/why-i-built-an-umbraco-package-that-does-nothing-27n1</guid>
      <description>&lt;p&gt;Well, maybe not &lt;em&gt;nothing&lt;/em&gt;, but definitely null.&lt;/p&gt;

&lt;p&gt;Consider this problem: You have a temperature property on a page, and that property is optional. How do you distinguish between no value, and 0°C in your code?&lt;/p&gt;

&lt;p&gt;By default, a blank decimal or integer property in the Umbraco backoffice will come out the other side of the default &lt;a href="https://our.umbraco.com/documentation/extending/property-editors/value-converters" rel="noopener noreferrer"&gt;property value converter&lt;/a&gt; with the default value of 0. This means to be able to differentiate between an empty value and 0 you need to do some extra work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used to do it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd86ee51o0cbcjgygqwv2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd86ee51o0cbcjgygqwv2.gif" alt="Animation of adding nested content in the Umbraco backoffice containing just one Temperature property" width="1899" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nested content. It works out-of-the-box and is very intentional. The property inside the nested content needs to be required and the editor can choose to "add" a temperature value, or leave it empty.&lt;/p&gt;

&lt;p&gt;But it's not pretty, and it's a rather clunky flow for editors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backoffice already knows the difference
&lt;/h2&gt;

&lt;p&gt;The backoffice already distinguishes between an empty value and a 0 in a decimal/integer property. In this screenshot Temperature is "0", but Temperature 2 is empty.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls41p98cs1ex0ey1o7us.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls41p98cs1ex0ey1o7us.png" alt="Two Umbraco decimal properties, one is empty, the other is zero." width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So all we need is a nice way of differentiating between empty and 0 values in our code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make those values &lt;code&gt;nullable&lt;/code&gt;!
&lt;/h2&gt;

&lt;p&gt;This is a good place to make use of nullable value types, because these &lt;em&gt;are&lt;/em&gt; nullable values! Umbraco even already stores them as NULL in the database. So instead of our decimal property returning a &lt;code&gt;decimal&lt;/code&gt; value type, we can make it return a &lt;code&gt;decimal?&lt;/code&gt; type.&lt;/p&gt;
&lt;h3&gt;
  
  
  Do it yourself
&lt;/h3&gt;

&lt;p&gt;To do this we'll need to use a new property value converter that can return a null.&lt;/p&gt;

&lt;p&gt;Inherit from (or use the decorator pattern) to make a new class that replaces the &lt;code&gt;ConvertSourceToIntermedia&lt;/code&gt; Method of the &lt;code&gt;Umbraco.Cms.Core.PropertyEditors.ValueConverters&lt;/code&gt; class. Making it nullable is actually quite straightforward.&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;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;ConvertSourceToIntermediate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IPublishedElement&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IPublishedPropertyType&lt;/span&gt; &lt;span class="n"&gt;propertyType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;)&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;source&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&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="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// is it already a decimal?&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;source&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;decimal&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="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// is it a double?&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;source&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;sourceDouble&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="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceDouble&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// is it a string?&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;source&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceString&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumberStyles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowDecimalPoint&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;NumberStyles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowLeadingSign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&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;decimal&lt;/span&gt; &lt;span class="n"&gt;d&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// couldn't convert the source value - default to null&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&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;My site makes use of &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references" rel="noopener noreferrer"&gt;Nullable Reference Types&lt;/a&gt; so, as I am returning null, I need to change the return type from &lt;code&gt;object&lt;/code&gt; to &lt;code&gt;object?&lt;/code&gt;. This means I can't simply inherit, so my class is actually a decorator:&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NullableDecimalConverter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPropertyValueConverter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPropertyValueConverter&lt;/span&gt; &lt;span class="n"&gt;coreConverter&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;DecimalValueConverter&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;You can see the full example of the class &lt;a href="https://github.com/JasonElkin/Emptiness/blob/f64cd94ca202787cdf59efa5c873ac31dd4281ef/src/Our.Umbraco.Emptiness/PropertyValueConverters/NullableDecimalConverter.cs" rel="noopener noreferrer"&gt;in my package's repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because property value converters are type-scanned at startup, and you can only only have one property value converter for a type at a time, you will need to replace &lt;code&gt;DecimalValueConverter&lt;/code&gt; with &lt;code&gt;NullableDecimalConverter&lt;/code&gt; at startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's a package for that
&lt;/h2&gt;

&lt;p&gt;As well as decimals, there are a number of other property types that return value types that should probably be able to return null values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date Picker - default value is 0001-01-01 00:00:00&lt;/li&gt;
&lt;li&gt;Integer - default value is 0&lt;/li&gt;
&lt;li&gt;Labels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I created a package called Emptiness:&lt;br&gt;
&lt;a href="https://github.com/JasonElkin/Emptiness" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2Fed6480e15ef66fe593be0961294dab37edb193df61c51583fa279d271a52759e%2FJasonElkin%2FEmptiness" alt="Umbraco Emptiness" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It adds nullable property value converters for the property types mentioned above, as well as a bonus...&lt;/p&gt;
&lt;h3&gt;
  
  
  Toggles (True/False)
&lt;/h3&gt;

&lt;p&gt;Though toggles don't have a null state in the UI, they can be empty if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A property has been added to a content type, but some content of that type has not been (re)published since. &lt;/li&gt;
&lt;li&gt;Content has been created via the API, without the property value having been set.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In these cases the following converters might be useful:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;YesNoDefaultConverter&lt;/code&gt; which returns the "Initial State" (default) value that has been configured in the property editor's settings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NullableYesNoConverter&lt;/code&gt;, returns null if content hasn't yet been (re)published with the property.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Just install it from NuGet and go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; dotnet add package Our.Umbraco.Emptiness
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a &lt;a href="https://github.com/JasonElkin/Emptiness#configuration" rel="noopener noreferrer"&gt;default configuration&lt;/a&gt; that I think will make sense for most sites. It makes empty integer, decimal and date pickers null when empty and makes Toggle's return their default value.&lt;/p&gt;

&lt;p&gt;After installing &amp;amp; configuring rebuild your ModelsBuilder models and you'll see the relevant properties have been made nullable (so you'd better null-check them 😉).&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback please 🙏
&lt;/h2&gt;

&lt;p&gt;The package is still pretty new, and I'm only using it in anger on one site, so I really welcome any &lt;a href="https://github.com/JasonElkin/Emptiness/issues" rel="noopener noreferrer"&gt;feedback/issues etc.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's somewhat different to what we're used to when working with properties in Umbraco, but I've found nullable values are a much better way of taking what's happening in the backoffice editor UI and surfacing that in our website code.&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Enabling non-unique node names in Umbraco 9</title>
      <dc:creator>Jason Elkin</dc:creator>
      <pubDate>Wed, 16 Mar 2022 09:41:45 +0000</pubDate>
      <link>https://forem.com/jasonelkin/enabling-non-unique-node-names-in-umbraco-9-3e6</link>
      <guid>https://forem.com/jasonelkin/enabling-non-unique-node-names-in-umbraco-9-3e6</guid>
      <description>&lt;p&gt;By default Umbraco doesn't let you have two documents with the same name underneath the same parent. I needed to change that.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're wondering why...
&lt;/h2&gt;

&lt;p&gt;(if not, skip to the how)&lt;/p&gt;

&lt;p&gt;In my particular case this is for news articles and event pages. &lt;/p&gt;

&lt;p&gt;My news and event pages use a custom &lt;code&gt;IUrlProvider&lt;/code&gt; and &lt;code&gt;IContentFinder&lt;/code&gt; (&lt;a href="https://our.umbraco.com/documentation/Reference/Routing/Request-Pipeline/" rel="noopener noreferrer"&gt;as per the docs&lt;/a&gt;) so that my news and events pages can have URLs like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/events/2022/03/16/my-awesome-event/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I have multiple events with the same name, but I want to keep all of my events underneath a single parent node in the backoffice. By default, if I create two events called "My Awesome Event" beneath the same node in the backoffice I'll end up with "My Awesome Event" and "My Awesome Event (1)", like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny23g4yfzpglxmi6tmac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny23g4yfzpglxmi6tmac.png" alt="enforced unique node names in the Umbraco backoffice" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The URL changes too:&lt;br&gt;
&lt;code&gt;/events/2022/03/16/my-awesome-event/&lt;/code&gt;&lt;br&gt;
&lt;code&gt;/events/2022/04/20/my-awesome-event-1/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I could use date "folders", and automate this by moving the articles/events on save, but this adds extra nodes and hierarchy that I don't want or need. Having them all in one list view will make for a much nicer editor experience (in this site).&lt;/p&gt;

&lt;p&gt;A bit of googling reminded me that in v7 we had the option to set &lt;code&gt;ensureUniqueNaming&lt;/code&gt; to false in the config. That's not available in v8, and was a bit of a blunt instrument anyway.&lt;/p&gt;

&lt;p&gt;Turns out that in v9 (and probably in v8 too, but I haven't checked) there's a smarter way...&lt;/p&gt;
&lt;h2&gt;
  
  
  Here's how &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;EnsureUniqueNaming&lt;/code&gt; is &lt;a href="https://github.com/umbraco/Umbraco-CMS/blob/bc80892f5114e7c284a61e86da36b777535aef68/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs#L90" rel="noopener noreferrer"&gt;still a property&lt;/a&gt; on the &lt;code&gt;DocumentRepository&lt;/code&gt; and it's used to determine if the repository allows non-unique names. It's set to false, and I thought about inheriting this class and simply setting this to true - but that would set it to true for &lt;em&gt;all&lt;/em&gt; content, and that seems risky. I have routing in place to make sure that non-unique names for my news/events don't matter, but &lt;del&gt;if&lt;/del&gt; &lt;em&gt;when&lt;/em&gt; an editor creates a page with a duplicate name elsewhere it just wont work.&lt;/p&gt;

&lt;p&gt;Fortunately, there's an &lt;a href="https://github.com/umbraco/Umbraco-CMS/blob/bc80892f5114e7c284a61e86da36b777535aef68/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs#L1623-L1626" rel="noopener noreferrer"&gt;overridable method&lt;/a&gt; that means we can be a bit smarter - &lt;code&gt;EnsureUniqueNodeName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this method I have access to the parent id, so I can check where I'm saving content and conditionally decide to allow duplicate node names or not. &lt;/p&gt;

&lt;p&gt;Here's what I ended up doing:&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomDocumentRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DocumentRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;allowNonUniqueChildNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;EventsPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelTypeAlias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NewsPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelTypeAlias&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// constructor ommited for brevity&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;EnsureUniqueNodeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;parentId&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;nodeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;parent&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="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentId&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;allowNonUniqueChildNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvariantContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&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;Alias&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="n"&gt;nodeName&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="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureUniqueNodeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&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;In this case the content type aliases are taken from my ModelsBuilder models.&lt;/p&gt;

&lt;p&gt;Thanks to Dependency Injection, it's super easy to replace the existing repository with our custom one. Here's an example using startup.cs, make sure to either add the service &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;AddUmbraco&lt;/code&gt; extension or add it to &lt;a href="https://our.umbraco.com/documentation/reference/using-ioc/#builder-extension-methods" rel="noopener noreferrer"&gt;your own builder extension method&lt;/a&gt; which is what I did.&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;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&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;AddUmbraco&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_env&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="nf"&gt;AddBackOffice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWebsite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddComposers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&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;AddUnique&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDocumentRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomDocumentRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;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 result:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1xf24noqto1p5hmkg5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1xf24noqto1p5hmkg5k.png" alt="duplicate node names in the Umbraco backoffice" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This could be better...
&lt;/h3&gt;

&lt;p&gt;This approach only really works where it makes sense to scope non-unique names based on the parent node - though that doesn't have to be based on type, it could be a property value etc. In my case, an "events" node only has child events and a "news" node only child news articles, so this is fine. &lt;/p&gt;

&lt;p&gt;Ideally, I'd want to take more things into account such as the type or even content of the node that I am saving, but this method doesn't know anything about the content other than the name. I could override other methods but that would result in a lot of code duplication (just look at all those private methods). Perhaps there's scope for adding a notification here to allow control over how and when unique names are applied, or at least overloading the method to take the content itself into account. If I get round to making a PR for that I'll update this post.&lt;/p&gt;

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