<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Jeff Sodeman]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://jeffsodeman.com/</link><image><url>https://jeffsodeman.com/favicon.png</url><title>Jeff Sodeman</title><link>https://jeffsodeman.com/</link></image><generator>Ghost 5.80</generator><lastBuildDate>Wed, 06 May 2026 11:52:15 GMT</lastBuildDate><atom:link href="https://jeffsodeman.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Conditional validation patterns]]></title><description><![CDATA[<p>Using spread</p><pre><code class="language-js">const formSchema = computed(() =&gt; {
    let schema = {
        name: yup.string().required().label(&quot;Name&quot;),
        email: yup.string(),
        quantity: yup.number().required().moreThan(0).lessThan(11).label(&quot;Quantity&quot;),
        productId: yup.number().required().label(&quot;Product&quot;),
        shipMethod: yup.string().required().label(&quot;Shipping Method&quot;),
    };

    // example of</code></pre>]]></description><link>https://jeffsodeman.com/conditional-validation-patterns/</link><guid isPermaLink="false">69176f6a11eff40001cd6297</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Fri, 14 Nov 2025 18:07:14 GMT</pubDate><content:encoded><![CDATA[<p>Using spread</p><pre><code class="language-js">const formSchema = computed(() =&gt; {
    let schema = {
        name: yup.string().required().label(&quot;Name&quot;),
        email: yup.string(),
        quantity: yup.number().required().moreThan(0).lessThan(11).label(&quot;Quantity&quot;),
        productId: yup.number().required().label(&quot;Product&quot;),
        shipMethod: yup.string().required().label(&quot;Shipping Method&quot;),
    };

    // example of turning off validation for a field hidden by an external variable
    // .when() can alternately be used to conditionally validate based on form values
    if (!hideEmail) {
        schema = {
            ...schema,
            email: yup.string().label(&quot;Email&quot;).email().required(),
        };
    }

    return yup.object(schema);
});</code></pre><p>Using ternaries</p><pre><code class="language-js">const formSchema = computed(() =&gt; {
    let schema = {
        name: yup.string().required().label(&quot;Name&quot;),
        email: hideEmail ? yup.string() : yup.string().label(&quot;Email&quot;).email().required(),
        quantity: yup.number().required().moreThan(0).lessThan(11).label(&quot;Quantity&quot;),
        productId: yup.number().required().label(&quot;Product&quot;),
        shipMethod: yup.string().required().label(&quot;Shipping Method&quot;),
    };

    return yup.object(schema);
});</code></pre>]]></content:encoded></item><item><title><![CDATA[PetaPoco SQL Date to C# DateOnly mapping]]></title><description><![CDATA[<pre><code class="language-c#">public class CustomMapper : ConventionMapper
{
	private static readonly Type DateOnlyType = typeof(DateOnly);
	private static readonly Type DateTimeType = typeof(DateTime);
	
	public override Func&lt;object, object&gt; GetFromDbConverter(PropertyInfo targetProperty, Type sourceType)
	{
		// see if there&apos;s already a converter on the class/property
		var result = base.GetFromDbConverter(targetProperty, sourceType);
        
		if (result</code></pre>]]></description><link>https://jeffsodeman.com/petapoco-sql-date-to-c-dateonly-mapping/</link><guid isPermaLink="false">68ffc1d811eff40001cd628a</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Mon, 27 Oct 2025 19:03:45 GMT</pubDate><content:encoded><![CDATA[<pre><code class="language-c#">public class CustomMapper : ConventionMapper
{
	private static readonly Type DateOnlyType = typeof(DateOnly);
	private static readonly Type DateTimeType = typeof(DateTime);
	
	public override Func&lt;object, object&gt; GetFromDbConverter(PropertyInfo targetProperty, Type sourceType)
	{
		// see if there&apos;s already a converter on the class/property
		var result = base.GetFromDbConverter(targetProperty, sourceType);
        
		if (result == null 
			&amp;&amp; targetProperty.PropertyType == DateOnlyType
			&amp;&amp; sourceType == DateTimeType)
		{            
            
			return o =&gt; DateOnly.FromDateTime((DateTime)o);
		}
		return result;
	}
	
	public override Func&lt;object, object&gt; GetToDbConverter(PropertyInfo sourceProperty)
	{
		// see if there&apos;s already a converter on the class/property
		var result = base.GetToDbConverter(sourceProperty);
        
		if (result == null 
			&amp;&amp; sourceProperty.PropertyType == DateOnlyType)
		{

			return o =&gt; ((DateOnly)o).ToDateTime(TimeOnly.MinValue);
		}
		return result;
	}
}</code></pre><pre><code class="language-c#">		var dbConfig = DatabaseConfiguration
				.Build()
				.UsingConnectionString(Environment.GetEnvironmentVariable(&quot;DbConnection&quot;))
				.UsingDefaultMapper(new CustomMapper())
				.UsingProvider&lt;SqlServerDatabaseProvider&gt;()
                ;</code></pre>]]></content:encoded></item><item><title><![CDATA[Azure Function gotchas]]></title><description><![CDATA[<p>If using <code>[FromBody]</code> model binding, include <code>using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;</code></p><p>otherwise the model binding will return null</p>]]></description><link>https://jeffsodeman.com/azure-function-gotchas/</link><guid isPermaLink="false">68effb3211eff40001cd627e</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Wed, 15 Oct 2025 20:13:26 GMT</pubDate><content:encoded><![CDATA[<p>If using <code>[FromBody]</code> model binding, include <code>using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;</code></p><p>otherwise the model binding will return null</p>]]></content:encoded></item><item><title><![CDATA[Fixing the white on white cursor in Google Docs]]></title><description><![CDATA[<p>From this post: <a href="https://issues.chromium.org/issues/40239916?ref=jeffsodeman.com#comment98">https://issues.chromium.org/issues/40239916#comment98</a></p><p>Add the following registry key and value</p><pre><code>[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Dwm]

&quot;OverlayTestMode&quot;=dword:00000005</code></pre>]]></description><link>https://jeffsodeman.com/fixing-the-white-on-white-cursor-in-google-docs/</link><guid isPermaLink="false">68c6d8a609388500018a9759</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Sun, 14 Sep 2025 18:25:59 GMT</pubDate><content:encoded><![CDATA[<p>From this post: <a href="https://issues.chromium.org/issues/40239916?ref=jeffsodeman.com#comment98">https://issues.chromium.org/issues/40239916#comment98</a></p><p>Add the following registry key and value</p><pre><code>[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Dwm]

&quot;OverlayTestMode&quot;=dword:00000005</code></pre>]]></content:encoded></item><item><title><![CDATA[Remove jobs from Hangfire]]></title><description><![CDATA[<pre><code class="language-sql">TRUNCATE TABLE [HangFire].[AggregatedCounter]
TRUNCATE TABLE [HangFire].[Counter]
TRUNCATE TABLE [HangFire].[JobParameter]
TRUNCATE TABLE [HangFire].[JobQueue]
TRUNCATE TABLE [HangFire].[List]
TRUNCATE TABLE [HangFire].[State]
DELETE FROM [HangFire].[Job]
DBCC CHECKIDENT (&apos;[HangFire].[Job]&apos;, reseed, 0)
UPDATE [HangFire].[Hash] SET Value = 1 WHERE Field = &apos;LastJobId&apos;</code></pre>]]></description><link>https://jeffsodeman.com/remove-jobs-from-hangfire/</link><guid isPermaLink="false">68b0763509388500018a974f</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Thu, 28 Aug 2025 15:31:27 GMT</pubDate><content:encoded><![CDATA[<pre><code class="language-sql">TRUNCATE TABLE [HangFire].[AggregatedCounter]
TRUNCATE TABLE [HangFire].[Counter]
TRUNCATE TABLE [HangFire].[JobParameter]
TRUNCATE TABLE [HangFire].[JobQueue]
TRUNCATE TABLE [HangFire].[List]
TRUNCATE TABLE [HangFire].[State]
DELETE FROM [HangFire].[Job]
DBCC CHECKIDENT (&apos;[HangFire].[Job]&apos;, reseed, 0)
UPDATE [HangFire].[Hash] SET Value = 1 WHERE Field = &apos;LastJobId&apos;</code></pre>]]></content:encoded></item><item><title><![CDATA[Change Scratch files location in Rider and Webstorm]]></title><description><![CDATA[<p>Help &gt; Edit Custom Properties</p><p><code>idea.scratch.path/scratches=G:/Dropbox/Work/ScratchFiles/Rider</code></p><p><code>idea.scratch.path/scratches=G:/Dropbox/Work/ScratchFiles/WebStorm</code></p>]]></description><link>https://jeffsodeman.com/change-scratch-files-location-in-rider-and-webstorm/</link><guid isPermaLink="false">68af1b9509388500018a9745</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Wed, 27 Aug 2025 14:53:22 GMT</pubDate><content:encoded><![CDATA[<p>Help &gt; Edit Custom Properties</p><p><code>idea.scratch.path/scratches=G:/Dropbox/Work/ScratchFiles/Rider</code></p><p><code>idea.scratch.path/scratches=G:/Dropbox/Work/ScratchFiles/WebStorm</code></p>]]></content:encoded></item><item><title><![CDATA[Fix SQL DECLARE variable use in DataGrip]]></title><description><![CDATA[<p>In 2025.1.3 you can&apos;t execute this script by default</p><pre><code class="language-SQL">DECLARE @id INT = 1;

SELECT * FROM Users WHERE id = @id;
GO</code></pre><p>In Settings &gt; Query Execution &gt; User Parameters, disable the pattern (?&lt;![@&lt;])@[a-zA-z0-9_]+&#xA0;</p>]]></description><link>https://jeffsodeman.com/fix-sql-declare-variable-use-in-datagrip/</link><guid isPermaLink="false">6849e72c3ff96c00012e7710</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Wed, 11 Jun 2025 20:31:49 GMT</pubDate><content:encoded><![CDATA[<p>In 2025.1.3 you can&apos;t execute this script by default</p><pre><code class="language-SQL">DECLARE @id INT = 1;

SELECT * FROM Users WHERE id = @id;
GO</code></pre><p>In Settings &gt; Query Execution &gt; User Parameters, disable the pattern (?&lt;![@&lt;])@[a-zA-z0-9_]+&#xA0;</p>]]></content:encoded></item><item><title><![CDATA[Using MirageJS to fake a .NET API]]></title><description><![CDATA[<p>By default Mirage sends models using a different style than .NET API. It also sends the model&apos;s Id as a string. To fix both problems add the following to <code>createServer</code></p><pre><code class="language-js">		serializers: {
			application: RestSerializer.extend({
				root: false,
				embed: true,
				valueForId(id) {
					return parseInt(id);
				},
			}),
		},</code></pre>]]></description><link>https://jeffsodeman.com/using-miragejs-to-fake-a-net-api/</link><guid isPermaLink="false">67e2cb1e3ff96c00012e76f3</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Tue, 25 Mar 2025 15:50:28 GMT</pubDate><content:encoded><![CDATA[<p>By default Mirage sends models using a different style than .NET API. It also sends the model&apos;s Id as a string. To fix both problems add the following to <code>createServer</code></p><pre><code class="language-js">		serializers: {
			application: RestSerializer.extend({
				root: false,
				embed: true,
				valueForId(id) {
					return parseInt(id);
				},
			}),
		},</code></pre>]]></content:encoded></item><item><title><![CDATA[Killing a Vite port]]></title><description><![CDATA[<p>Run <code>npx kill-port 8080</code> etc</p>]]></description><link>https://jeffsodeman.com/killing-a-vite-port/</link><guid isPermaLink="false">67e2cac63ff96c00012e76ea</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Tue, 25 Mar 2025 15:25:44 GMT</pubDate><content:encoded><![CDATA[<p>Run <code>npx kill-port 8080</code> etc</p>]]></content:encoded></item><item><title><![CDATA[Fixing Chrome "Failed to read the cssRules" errors]]></title><description><![CDATA[<p>At some point Chrome got picky about using &lt;link&gt; tags to load fonts from external domains for CORS reasons.</p><p>To fix, just add <code>crossorigin=&quot;anonymous&quot;</code> to the link tag.</p>]]></description><link>https://jeffsodeman.com/fixing-chrome/</link><guid isPermaLink="false">67e2ca2d3ff96c00012e76db</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Tue, 25 Mar 2025 15:24:39 GMT</pubDate><content:encoded><![CDATA[<p>At some point Chrome got picky about using &lt;link&gt; tags to load fonts from external domains for CORS reasons.</p><p>To fix, just add <code>crossorigin=&quot;anonymous&quot;</code> to the link tag.</p>]]></content:encoded></item><item><title><![CDATA[Cleaning git after .gitignore changes]]></title><description><![CDATA[<pre><code class="language-bash">git rm -r --cached .
git add .
git commit -m &quot;Drop files from .gitignore&quot;</code></pre>]]></description><link>https://jeffsodeman.com/cleaning-git-after-gitignore-changes/</link><guid isPermaLink="false">6793fcc83ff96c00012e76cf</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Fri, 24 Jan 2025 20:49:44 GMT</pubDate><content:encoded><![CDATA[<pre><code class="language-bash">git rm -r --cached .
git add .
git commit -m &quot;Drop files from .gitignore&quot;</code></pre>]]></content:encoded></item><item><title><![CDATA[Parsing numbers from strings in SQL]]></title><description><![CDATA[<pre><code class="language-sql">SELECT Id, PetCount,
ISNULL(SUBSTRING(PetCount,
PATINDEX(&apos;%[0-9]%&apos;, PetCount),
(CASE WHEN PATINDEX(&apos;%[^0-9]%&apos;, STUFF(PetCount, 1, (PATINDEX(&apos;%[0-9]%&apos;, PetCount) - 1), &apos;&apos;)) = 0
THEN LEN(PetCount) ELSE (PATINDEX(&apos;%[^0-9]%&apos;, STUFF(PetCount, 1, (PATINDEX(&apos;%[0-9]%&apos;, PetCount) - 1), &apos;&apos;</code></pre>]]></description><link>https://jeffsodeman.com/parsing-numbers-from-strings-in-sql/</link><guid isPermaLink="false">660c5e72821b140001cfa55c</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Tue, 02 Apr 2024 19:39:02 GMT</pubDate><content:encoded><![CDATA[<pre><code class="language-sql">SELECT Id, PetCount,
ISNULL(SUBSTRING(PetCount,
PATINDEX(&apos;%[0-9]%&apos;, PetCount),
(CASE WHEN PATINDEX(&apos;%[^0-9]%&apos;, STUFF(PetCount, 1, (PATINDEX(&apos;%[0-9]%&apos;, PetCount) - 1), &apos;&apos;)) = 0
THEN LEN(PetCount) ELSE (PATINDEX(&apos;%[^0-9]%&apos;, STUFF(PetCount, 1, (PATINDEX(&apos;%[0-9]%&apos;, PetCount) - 1), &apos;&apos;))) - 1
END )
), null) AS Cleaned
FROM dbo.MyTable WHERE (PetCount IS NOT NULL OR PetCountLegacy IS NOT NULL) AND LEN(PetCount) &gt; 1</code></pre>]]></content:encoded></item><item><title><![CDATA[Refresh Rider solution analysis after Git branch change]]></title><description><![CDATA[<p>Sometimes non-existent errors are shown after changing branches while a solution is open in Rider.</p><p>Run this action:</p><p>Ctrl+Shift+A, &quot;Reanalyze All&quot;</p><p>Or invalidate the cache:</p><p>File &gt; Invalidate Caches</p>]]></description><link>https://jeffsodeman.com/refresh-rider-solution-analysis-after-git-branch-change/</link><guid isPermaLink="false">65f1b7df821b140001cfa54a</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Wed, 13 Mar 2024 14:36:06 GMT</pubDate><content:encoded><![CDATA[<p>Sometimes non-existent errors are shown after changing branches while a solution is open in Rider.</p><p>Run this action:</p><p>Ctrl+Shift+A, &quot;Reanalyze All&quot;</p><p>Or invalidate the cache:</p><p>File &gt; Invalidate Caches</p>]]></content:encoded></item><item><title><![CDATA[Disable watch in Vue 2 when mutating data]]></title><description><![CDATA[<p>This is a mixin for Vue 2</p><pre><code class="language-js">export default {
	methods: {
		$withoutWatchers(cb) {
			const watcher = {
				cb: this._watcher.cb,
				sync: this._watcher.sync,
			};

			this._watcher = Object.assign(this._watcher, { cb: () =&gt; null, sync: true });

			cb();

			this._watcher = Object.assign(this._watcher, watcher);
		},
	},
};</code></pre><p>You can then use the method by passing in</p>]]></description><link>https://jeffsodeman.com/disable-watch-in-vue-2-when-mutating-data/</link><guid isPermaLink="false">652c0997f1c0610001e357ec</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Sun, 15 Oct 2023 15:51:37 GMT</pubDate><content:encoded><![CDATA[<p>This is a mixin for Vue 2</p><pre><code class="language-js">export default {
	methods: {
		$withoutWatchers(cb) {
			const watcher = {
				cb: this._watcher.cb,
				sync: this._watcher.sync,
			};

			this._watcher = Object.assign(this._watcher, { cb: () =&gt; null, sync: true });

			cb();

			this._watcher = Object.assign(this._watcher, watcher);
		},
	},
};</code></pre><p>You can then use the method by passing in the mutation</p><pre><code class="language-js">methods: {
    sample () {
      this.$withoutWatchers(() =&gt; {
        // mutation goes here
        // ex: this.foo = &quot;bar&quot;;
      })
    }
  },</code></pre><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/vuejs/vue/issues/1829?ref=jeffsodeman.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ability to disable/not trigger watch handler on data? &#xB7; Issue #1829 &#xB7; vuejs/vue</div><div class="kg-bookmark-description">For my application, I&#x2019;m mutating the object in my data: [{...}, {...}, {...}] to state changes occurring at other open instances of my application (happening through web sockets etc, etc). I have a...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">vuejs</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/806d9b6793b8f688cca02ab21aff70c31db8ec9e96f23774925ea34ba39dde8f/vuejs/vue/issues/1829" alt></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Broken WordPress editing due to WPS Hide Login plugin]]></title><description><![CDATA[<p>Page editing was broken in a WP site that I was asked to update. Unsurprisingly there were a lot of plugins running and it was difficult to track down the reason the Beaver Builder page editor was failing. In the console there were a lot of 403 permissions errors coming</p>]]></description><link>https://jeffsodeman.com/wps-hide-login/</link><guid isPermaLink="false">63b6f990ac58440001362d84</guid><category><![CDATA[Development Notes]]></category><dc:creator><![CDATA[Jeff Sodeman]]></dc:creator><pubDate>Thu, 05 Jan 2023 16:33:05 GMT</pubDate><content:encoded><![CDATA[<p>Page editing was broken in a WP site that I was asked to update. Unsurprisingly there were a lot of plugins running and it was difficult to track down the reason the Beaver Builder page editor was failing. In the console there were a lot of 403 permissions errors coming back from the WP API.</p><p>To help with debugging I set up a copy of the site. As part of the Duplicator Pro install process it disabled the WPS Hide Login plugin. On first login to the test site I was prompted to confirm the admin contact information. Once logged in I found that the page editor was working on the test site.</p><p>On the prod site I tried disabling the WPS Hide Login plugin, but it didn&apos;t seem to fix the problem. After I cleared all the cookies etc I logged into prod again and was prompted to confirm the admin contact information. After doing this the page editor began working. I was then able to turn WPS Hide Login back in and everything worked as expected.</p><p>I suspect that the login redirection from the plugin was skipping the contact confirmation, and that the lack of being confirmed was somehow messing with the API access.</p>]]></content:encoded></item></channel></rss>