<?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[Jimmy Bogard]]></title><description><![CDATA[I'm Jimmy Bogard, a software architect and consultant. I created the OSS libraries AutoMapper, MediatR, and Respawn. I help teams build systems faster, better, and more maintainable.]]></description><link>https://www.jimmybogard.com/</link><image><url>https://www.jimmybogard.com/favicon.png</url><title>Jimmy Bogard</title><link>https://www.jimmybogard.com/</link></image><generator>Ghost 6.25</generator><lastBuildDate>Thu, 02 Apr 2026 20:07:07 GMT</lastBuildDate><atom:link href="https://www.jimmybogard.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[AutoMapper 16.1.1 Released]]></title><description><![CDATA[<p>This release is a patch release to fix a thread deadlock and security issue. From the <a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v16.1.1?ref=jimmybogard.com" rel="noreferrer">release notes</a>:</p><h2 id="thread-deadlock">Thread Deadlock</h2><p>Thanks to <a href="https://github.com/t0m-4?ref=jimmybogard.com" rel="noreferrer">@t0m-4</a> for reporting <a href="https://github.com/LuckyPennySoftware/AutoMapper/issues/4612?ref=jimmybogard.com" rel="noreferrer">this issue</a>, which due to Microsoft deprecating some of the &quot;sync&quot; APIs for decryption, led to potential thread starvation and locking issues. The</p>]]></description><link>https://www.jimmybogard.com/automapper-16-1-1-released/</link><guid isPermaLink="false">69b43110ab858700018eaa7c</guid><category><![CDATA[AutoMapper]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Fri, 13 Mar 2026 15:58:13 GMT</pubDate><content:encoded><![CDATA[<p>This release is a patch release to fix a thread deadlock and security issue. From the <a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v16.1.1?ref=jimmybogard.com" rel="noreferrer">release notes</a>:</p><h2 id="thread-deadlock">Thread Deadlock</h2><p>Thanks to <a href="https://github.com/t0m-4?ref=jimmybogard.com" rel="noreferrer">@t0m-4</a> for reporting <a href="https://github.com/LuckyPennySoftware/AutoMapper/issues/4612?ref=jimmybogard.com" rel="noreferrer">this issue</a>, which due to Microsoft deprecating some of the &quot;sync&quot; APIs for decryption, led to potential thread starvation and locking issues. The update still has to use a &quot;sync-over-async&quot; pattern, but does so in a much safer manner.</p><h2 id="security">Security</h2><p>We fixed an issue where certain cyclic or self-referential object graphs could trigger uncontrolled recursion during mapping, potentially resulting in stack exhaustion and denial of service.</p><p>Applications that process untrusted or attacker-controlled object graphs through affected mapping paths may be impacted.</p><p>Users should upgrade to this release.</p><p>Security advisory: <a href="https://github.com/LuckyPennySoftware/AutoMapper/security/advisories/GHSA-rvv3-g6hj-g44x?ref=jimmybogard.com" rel="noreferrer">GHSA-rvv3-g6hj-g44x</a></p><p>Thanks to <a href="https://github.com/skdishansachin?ref=jimmybogard.com" rel="noreferrer">@skdishansachin</a> for responsibly disclosing this issue.</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper 16.1 and MediatR 14.1 Released]]></title><description><![CDATA[<p>Today I released AutoMapper 16.1 and MediatR 14.1 (as part of now regular quarterly releases):</p><ul><li><a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v16.1.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper Release Notes</a></li><li><a href="https://github.com/LuckyPennySoftware/MediatR/releases/tag/v14.1.0?ref=jimmybogard.com" rel="noreferrer">MediatR Release Notes</a></li></ul><p>AutoMapper added some interesting features, allowing for factories and conditions to use dependencies (previously only allowing <code>Func</code>-based callbacks. On the MediatR side, we added support for</p>]]></description><link>https://www.jimmybogard.com/automapper-16-1-and-mediatr-14-1-released/</link><guid isPermaLink="false">69a7566e8d8512000133dd6b</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Tue, 03 Mar 2026 21:50:55 GMT</pubDate><content:encoded><![CDATA[<p>Today I released AutoMapper 16.1 and MediatR 14.1 (as part of now regular quarterly releases):</p><ul><li><a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v16.1.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper Release Notes</a></li><li><a href="https://github.com/LuckyPennySoftware/MediatR/releases/tag/v14.1.0?ref=jimmybogard.com" rel="noreferrer">MediatR Release Notes</a></li></ul><p>AutoMapper added some interesting features, allowing for factories and conditions to use dependencies (previously only allowing <code>Func</code>-based callbacks. On the MediatR side, we added support for more interesting generic use cases in complex generic hierarchies as well as a number of bugs squashed.</p><p>You can get the latest drops on <a href="https://www.nuget.org/profiles/LuckyPennySoftware?ref=jimmybogard.com" rel="noreferrer">NuGet</a>. And a little celebration is in order - AutoMapper hit 1 billion downloads &#x1F973;</p><p>Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper 16.0.0 and MediatR 14.0.0 Released with .NET 10 Support]]></title><description><![CDATA[<p>With the release of .NET, we&apos;ve released updated packages of AutoMapper and MediatR targeting .NET 10 (and all supported versions of .NET and 4.x of .NET Framework).</p><p>From this release forward, we&apos;re aligning to major release cadences of .NET since this also means upping our</p>]]></description><link>https://www.jimmybogard.com/automapper-16-0-0-and-mediatr-14-0-0-released-with-net-10-support/</link><guid isPermaLink="false">6930a56fd1ac720001989230</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 03 Dec 2025 21:23:28 GMT</pubDate><content:encoded><![CDATA[<p>With the release of .NET, we&apos;ve released updated packages of AutoMapper and MediatR targeting .NET 10 (and all supported versions of .NET and 4.x of .NET Framework).</p><p>From this release forward, we&apos;re aligning to major release cadences of .NET since this also means upping our dependency versions as well. Minor versions will still happen throughout the year but upping dependencies often has just as many issues as breaking API changes so we want to align with that cadence to make it easier for folks to understand when things are changing.</p><p>This release doesn&apos;t have any API changes from the previous minor release, but we did add <a href="https://learn.microsoft.com/en-us/nuget/create-packages/sign-a-package?ref=jimmybogard.com" rel="noreferrer">NuGet package signing</a>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.jimmybogard.com/content/images/2025/12/image.png" class="kg-image" alt loading="lazy" width="1116" height="704" srcset="https://www.jimmybogard.com/content/images/size/w600/2025/12/image.png 600w, https://www.jimmybogard.com/content/images/size/w1000/2025/12/image.png 1000w, https://www.jimmybogard.com/content/images/2025/12/image.png 1116w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">NuGet package information with digital signatures</span></figcaption></figure><p>This shows that the AutoMapper (and MediatR) packages were published by <a href="https://luckypennysoftware.com/?ref=jimmybogard.com" rel="noreferrer">Lucky Penny Software</a> (my company) and not...Unlucky Penny Software? Getting this additional security verification is important for customers to know that this package comes from us and not some other entity. Getting verified for code signing certificates is a fairly involved process doing things like &quot;taking selfies with my passport&quot;.</p><p>Downloads and release notes below:</p><ul><li><a href="https://www.nuget.org/packages/automapper?ref=jimmybogard.com" rel="noreferrer">AutoMapper NuGet</a></li><li><a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v16.0.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper v16.0.0 Release Notes</a></li><li><a href="https://www.nuget.org/packages/mediatr?ref=jimmybogard.com" rel="noreferrer">MediatR NuGet</a></li><li><a href="https://github.com/LuckyPennySoftware/MediatR/releases/tag/v14.0.0?ref=jimmybogard.com" rel="noreferrer">MediatR v14.00 Release Notes</a></li></ul><p>Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper 15.1 and MediatR 13.1 Released]]></title><description><![CDATA[<p>Starting a new policy of regular quarterly releases, today I pushed out new versions of AutoMapper and MediatR:</p><ul><li><a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v15.1.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper 15.1.0 Release Notes</a></li><li><a href="https://github.com/LuckyPennySoftware/MediatR/releases/tag/v13.1.0?ref=jimmybogard.com" rel="noreferrer">MediatR 13.1.0 Release Notes</a></li></ul><p>The previous versions restored <code>netstandard2.0</code> support across the board. In these releases, I&apos;m including first-class support for</p>]]></description><link>https://www.jimmybogard.com/automapper-15-1-and-mediatr-13-1-released/</link><guid isPermaLink="false">68fbbf1dda4c5b0001a27ad6</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Mon, 03 Nov 2025 22:06:16 GMT</pubDate><content:encoded><![CDATA[<p>Starting a new policy of regular quarterly releases, today I pushed out new versions of AutoMapper and MediatR:</p><ul><li><a href="https://github.com/LuckyPennySoftware/AutoMapper/releases/tag/v15.1.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper 15.1.0 Release Notes</a></li><li><a href="https://github.com/LuckyPennySoftware/MediatR/releases/tag/v13.1.0?ref=jimmybogard.com" rel="noreferrer">MediatR 13.1.0 Release Notes</a></li></ul><p>The previous versions restored <code>netstandard2.0</code> support across the board. In these releases, I&apos;m including first-class support for <code>net462</code> where I&apos;m building and testing on both *nix and Windows. We noticed that just including <code>netstandard2.0</code> as a target framework wasn&apos;t enough to really guarantee that the libraries would work against full .NET, so we now build and test on both.</p><p>There&apos;s some new features, enhancements, and bug fixes that you can view in the release notes above.</p><p>Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper and MediatR Roadmaps]]></title><description><![CDATA[<p>One of my main goals of commercialization of AutoMapper and MediatR was being able to finally invest time in these projects where basically all new work stopped when I lost corporate sponsorship. I wanted to take some time to share where I&apos;d like to take these projects now</p>]]></description><link>https://www.jimmybogard.com/automapper-and-mediatr-roadmaps/</link><guid isPermaLink="false">686d2b383a65e80001b759d6</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Tue, 08 Jul 2025 15:15:32 GMT</pubDate><content:encoded><![CDATA[<p>One of my main goals of commercialization of AutoMapper and MediatR was being able to finally invest time in these projects where basically all new work stopped when I lost corporate sponsorship. I wanted to take some time to share where I&apos;d like to take these projects now that I have that sponsorship back.</p><h3 id="tracking-official-net-support">Tracking official .NET support</h3><p>Firstly, the latest releases bring back <code>netstandard2.0</code> support for both AutoMapper and MediatR which had dropped both years ago. MediatR was actually still on <code>net6.0</code> prior to this release which was already out of support for months.</p><p>It wasn&apos;t exactly easy, especially because of how much <code>net8.0</code> and <code>net9.0</code> have diverged from <code>netstandard2.0</code> not just in terms of APIs but C# language features, but having been part of several ASP.NET 4.x to ASP.NET Core migrations, having <code>netstandard2.0</code> support makes this transition quite a bit easier. In the past we&apos;d have to conditionally reference packages because there was no longer a common package version between say .NET 8 and .NET 4.8. That&apos;s something that I wish I had before that now I do.</p><h3 id="automapper-roadmap">AutoMapper Roadmap</h3><p>One of the biggest complaints I hear about AutoMapper is that it&apos;s hard to debug - you&apos;re trading compile-time errors for runtime exceptions. We spent a LOT of time baking in better exception handling into the expression trees generated (resulting in worse performance, but better diagnostics), but that isn&apos;t always enough.</p><p>The answer here is <strong>source generators</strong>, but I&apos;m not interested in merely copying other library&apos;s approaches. What I want to target is source generators that:</p><ul><li>Plug in to AutoMapper&apos;s rich extensibility model</li><li>Stay true to AutoMapper&apos;s <a href="https://www.jimmybogard.com/automappers-design-philosophy/" rel="noreferrer">design philosophy</a> </li><li>Support IQueryables (my favorite feature)</li><li>Track the features of AutoMapper&apos;s in-memory mapping</li><li>Support mapping validation (critical for any mapping tool)</li></ul><p>Debuggability is my main focus here, although obviously performance would be a secondary win. Source generators have come a LONG way since I first looked at them when they were first released, so I&apos;m excited to extend AutoMapper&apos;s functionality in this area.</p><p>This one is pretty big, so that&apos;s going to be my focus initially.</p><h3 id="mediatr-roadmap">MediatR Roadmap</h3><p>Some folks have asked or even pointed to other libraries that do source generation of basically a copy of MediatR&apos;s API. I am looking at that, but there&apos;s been quite a few things on MediatR&apos;s backlog that I want to look at first. Source generation in mediators I find a bit less interesting in real-world projects, outside of philosophical debates.</p><p>MediatR is commonly used in concert with <a href="https://www.jimmybogard.com/vertical-slice-architecture/" rel="noreferrer">Vertical Slice Architecture</a>, and a number of its features came out of using it in these scenarios (like behaviors). Today, a lot of features are tied in to the feature set of the stock Microsoft DI container. Unfortunately, features are only really added to that container if the ASP.NET Core team needs them. Even my PR to support generic constraints took like 5 years to merge in.</p><p>Moving away from relying on those DI features would mean I could do much more interesting things in the &quot;application use case pipeline&quot; that aren&apos;t possible with C#/DI alone, like:</p><ul><li><strong>Applying behaviors based on customized policies</strong></li><li><strong>Baking in support for result patterns</strong></li><li><strong>Direct support for application use cases</strong><ul><li>Blazor (sending a request from the client to a handler on the server)</li><li>Minimal APIs (scaffolding to separate API logic from application logic)</li><li>Domain events via notifications and EF/other ORMs</li></ul></li></ul><p>The idea of behaviors came from reviewing many production systems using MediatR and folding in that into first-class features. I am going to continue on this track.</p><p>What else are you interested in?</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper and MediatR Commercial Editions Launch Today]]></title><description><![CDATA[<p>Today I&apos;m excited to announce the official launch and release of the commercial editions of AutoMapper and MediatR. Both of these libraries have moved under their new corporate owner (me), <a href="https://luckypennysoftware.com/?ref=jimmybogard.com" rel="noreferrer">Lucky Penny Software</a>. I formed this company to house these projects separate from my consulting company, but it&</p>]]></description><link>https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/</link><guid isPermaLink="false">68643ac78da7830001ca587c</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 02 Jul 2025 15:00:12 GMT</pubDate><content:encoded><![CDATA[<p>Today I&apos;m excited to announce the official launch and release of the commercial editions of AutoMapper and MediatR. Both of these libraries have moved under their new corporate owner (me), <a href="https://luckypennysoftware.com/?ref=jimmybogard.com" rel="noreferrer">Lucky Penny Software</a>. I formed this company to house these projects separate from my consulting company, but it&apos;s just me there, I&apos;m the sole corporate overlord.</p><p>The GitHub repositories have transferred to the new GitHub organization (along with their ownership) here:</p><ul><li><a href="https://github.com/luckypennysoftware/automapper?ref=jimmybogard.com" rel="noreferrer">LuckyPennySoftware/AutoMapper</a></li><li><a href="https://github.com/luckypennysoftware/mediatr?ref=jimmybogard.com" rel="noreferrer">LuckyPennySoftware/MediatR</a></li></ul><p>With these, I&apos;ve launched new home pages for each library:</p><ul><li><a href="https://automapper.io/?ref=jimmybogard.com" rel="noreferrer">https://automapper.io</a></li><li><a href="https://mediatr.io/?ref=jimmybogard.com" rel="noreferrer">https://mediatr.io</a></li></ul><p>As well as a storefront site to purchase and manage licenses at:</p><ul><li><a href="https://luckypennysoftware.com/?ref=jimmybogard.com" rel="noreferrer">https://luckypennysoftware.com</a></li></ul><p>It&apos;s quite a bit to dig in to, so let&apos;s go over the details!</p><h3 id="whats-the-new-license">What&apos;s the new license?</h3><p>As <a href="https://www.jimmybogard.com/automapper-and-mediatr-licensing-update/" rel="noreferrer">discussed before</a>, I wanted to release these libraries under a <a href="https://github.com/LuckyPennySoftware/AutoMapper/blob/master/LICENSE.md?ref=jimmybogard.com" rel="noreferrer">dual-license model</a>:</p><ul><li><a href="https://opensource.org/license/rpl-1-5/?ref=jimmybogard.com" rel="noreferrer">Reciprocal Public License 1.5 (RPL1.5)</a></li><li><a href="https://luckypennysoftware.com/license?ref=jimmybogard.com" rel="noreferrer">Lucky Penny Software Commercial License</a></li></ul><p>It&apos;s a common dual-license model that many other OSS companies have chosen (MongoDB etc.) and had success with.</p><p>Under the commercial license, I&apos;ve created a <strong>tier-based licensing model</strong> based on <strong>team size</strong>. There are <strong>no individual per-seat licenses</strong>, only licensing based on the number of developers.</p><h3 id="how-much-will-it-cost">How much will it cost?</h3><p>With a tier-based pricing approach, I wanted a pricing model that scales with team size and allows for company growth without a lot of hassle. There are 3 paid tiers:</p><ul><li>Standard - <strong>1-10 developers</strong></li><li>Professional - <strong>11-50 developers</strong></li><li>Enterprise - <strong>Unlimited developers</strong></li></ul><p>Pricing is a <strong>subscription model</strong>, with both monthly and annual subscriptions (with a discount for annual subscriptions), as well as an option to <strong>bundle both libraries</strong> at a discount. You can find the details here (with all options), priced to your currency or in USD:</p><ul><li><a href="https://automapper.io/?ref=jimmybogard.com#pricing" rel="noreferrer">AutoMapper pricing</a></li><li><a href="https://mediatr.io/?ref=jimmybogard.com#pricing" rel="noreferrer">MediatR pricing</a></li></ul><p>You can also find the details of what subscription benefits you&apos;ll get at the links above, including:</p><ul><li>Private Discord channels</li><li>Priority support</li><li>Early access to new releases</li><li>Support for all currently supported versions of .NET Framework 4.x and .NET (<code>netstandard2.0</code>, <code>net8.0</code>, <code>net9.0</code>)</li><li>And more (as I build it)</li></ul><p>All subscription payments are managed through <a href="https://www.paddle.com/?ref=jimmybogard.com" rel="noreferrer">Paddle</a>, which supports...many different countries, currencies, and payment providers.</p><h3 id="do-you-have-free-licenses-for-insert-situation-here">Do you have free licenses for &lt;insert situation here&gt;? </h3><p>Yes! Besides the RPL license, I&apos;m also including a <strong>Community edition</strong> under the Commercial license that is <strong>free</strong> for:</p><ul><li>Companies and individuals <strong>under $5,000,000</strong> in gross annual revenue</li><li>Non-profits <strong>under $5,000,000</strong> in annual total budget (expenditure)</li><li>Educational/classroom use</li><li>Non-production environments</li></ul><p>You&apos;re still required to register for a license key, but this is only for auditing purposes.</p><h3 id="how-do-i-get-the-commercial-versions">How do I get the commercial versions?</h3><p>To make everyone&apos;s lives easier, these new major versions of AutoMapper and MediatR on NuGet are released under the new dual license agreement:</p><ul><li><a href="https://www.nuget.org/packages/AutoMapper/15.0.0?ref=jimmybogard.com" rel="noreferrer">AutoMapper v15.0</a></li><li><a href="https://www.nuget.org/packages/MediatR/13.0.0?ref=jimmybogard.com" rel="noreferrer">MediatR v13.0</a></li></ul><p>When you install these versions, you&apos;ll now be prompted for license acceptance. Once you obtain a license key, you&apos;ll be able to set the license key as:</p><pre><code class="language-csharp">services.AddAutoMapper(cfg =&gt; /* or AddMediatR */
    cfg.LicenseKey = &quot;&lt;License key here&gt;&quot;;
});</code></pre><p>I don&apos;t restrict usage of these products with a missing/invalid/expired license key, but you&apos;ll see some messages in your logs prompting you to supply a valid key.</p><h3 id="what-about-the-existing-versions">What about the existing versions?</h3><p>I&apos;ve created archived versions of the final releases of these two libraries:</p><ul><li><a href="https://github.com/automapper/automapper.archive?ref=jimmybogard.com" rel="noreferrer">AutoMapper/AutoMapper.Archive</a></li><li><a href="https://github.com/jbogard/mediatr.archive?ref=jimmybogard.com" rel="noreferrer">jbogard/MediatR.Archive</a></li></ul><p>Per those existing license agreements, you&apos;re free to fork, download, print out and read by the fireplace. Those archives will live on for anyone to use as they like.</p><p>If you&apos;re an existing user, you don&apos;t need to do anything. The existing NuGet packages (prior to the major versions listed above) are bound by the license agreements at the time of their release and will also live on.</p><h3 id="why-lucky-penny">Why Lucky Penny?</h3><p>Because she was my first dog! Although she&apos;s no longer with us anymore, I loved her spunk and her spirit and wanted to honor her memory with my company name (and logo). Here she is judging, always judging:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.jimmybogard.com/content/images/2025/07/DSC00167-copy.jpeg" class="kg-image" alt loading="lazy" width="1015" height="968" srcset="https://www.jimmybogard.com/content/images/size/w600/2025/07/DSC00167-copy.jpeg 600w, https://www.jimmybogard.com/content/images/size/w1000/2025/07/DSC00167-copy.jpeg 1000w, https://www.jimmybogard.com/content/images/2025/07/DSC00167-copy.jpeg 1015w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Penny the dog</span></figcaption></figure><p>I named her Penny because 1) she was found by the side of a busy highway miles from anywhere (lucky for both of us) and 2) her copper color. So, Lucky Penny Software!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.jimmybogard.com/content/images/2025/07/logo_wide.png" class="kg-image" alt loading="lazy" width="270" height="100"><figcaption><span style="white-space: pre-wrap;">Lucky Penny Software logo</span></figcaption></figure><p>It&apos;s been a long journey to get here but I&apos;m excited about what the future holds for these libraries that have amassed more than 1.1 billion downloads. Thanks everyone for your patience and support as I worked to launch!</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper and MediatR Licensing Update]]></title><description><![CDATA[<p>In my <a href="https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/" rel="noreferrer">last post</a>, I shared the news that I&apos;ve decided to take a commercialization route for AutoMapper and MediatR to ensure their long-term success. While that post was heavy on the motivation, it was intentionally light on the details. I did share that I wanted to be</p>]]></description><link>https://www.jimmybogard.com/automapper-and-mediatr-licensing-update/</link><guid isPermaLink="false">67f9819b43cc810001fffa0b</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><category><![CDATA[OSS]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 16 Apr 2025 05:15:28 GMT</pubDate><content:encoded><![CDATA[<p>In my <a href="https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/" rel="noreferrer">last post</a>, I shared the news that I&apos;ve decided to take a commercialization route for AutoMapper and MediatR to ensure their long-term success. While that post was heavy on the motivation, it was intentionally light on the details. I did share that I wanted to be transparent on that process, and this post is part of that transparency.</p><p>There is a TON of information out there on possible models for sustainable open source, such as:</p><ul><li>Consulting services</li><li>Open core</li><li>Hosted services</li><li>Dual license</li><li>a dozen others</li></ul><p>Of course besides my previous situation, &quot;be fortunate enough to work at a place that values and directly sponsors your work.&quot; This is the easiest place to be, but for projects that reach some threshold of users/downloads/complexity, maintainers must rely on sponsorship in some form or fashion. And when that sponsorship goes away for whatever reason, well, here we are.</p><p>Of the many options available, the most viable option is to <strong>move AutoMapper and MediatR to a </strong><a href="https://en.wikipedia.org/wiki/Multi-licensing?ref=jimmybogard.com" rel="noreferrer"><strong>dual license model</strong></a>.<strong> </strong>This looks to be the best choice after carefully examining the options and consulting with many other OSS maintainers who have already made this journey.</p><h3 id="dual-licensing-model">Dual Licensing Model</h3><p>When I first started thinking about how I might go about this, I asked myself, &quot;who bears the most responsibility in ensuring the sustainability of the OSS projects on which they depend?&quot; which is a long winded way of saying &quot;who should pay?&quot; But another way of thinking of this is &quot;who should NOT pay?&quot; Looking at how others do this as well as how I want to approach it, <strong>I want to make these libraries free for</strong>:</p><ul><li>Developers using it in an OSS setting</li><li>Individuals/students/hobbyists (using AutoMapper for fun not profit)</li><li>Non-profit/charities (maybe not for fun but also not for profit)</li><li>Startups or small companies (below some revenue/funding threshold)</li><li>Non-commercial setting (this I&apos;m not sure is absolutely necessary with the other categories)</li><li>Non-production environments (instead of any trial period etc.)</li></ul><p>I don&apos;t know if this exact verbiage is what will be the end result, but this is my overall goal.</p><p>Then for who I&apos;m targeting for paid for-licenses, it&apos;s <strong>for-profit businesses using these libraries for commercial activities</strong>. Looking at my clients over the years who&apos;ve used my libraries, it&apos;s a mix of these free/commercial categories.</p><p>In terms of a model for commercial licensing, I want to ensure that paid licenses add value beyond &quot;I can download the license.&quot; This is the more fun part of this exercise for me, where I can try the things I never really could before without a more direct form of sponsorship/funding. I have a lot of ideas here, but nothing ready to share yet. If <em>you</em> have an idea of &quot;if my company paid for a license, what else would I want to have included?&quot; I would love to hear about it!</p><p>I am looking at a <strong>tiered license model</strong> but <strong>no per-seat licenses</strong>. I don&apos;t want to charge individual developers anything&#x2014;that seems like a pain for everyone involved and I&apos;m trying to keep things simple. &quot;A new developer gets onboarded and now we need a new license&quot; is too much for me to deal with and goes against the spirit of these libraries&#x2014;the benefit is to the entire team, regardless of the number of developers.</p><p>I don&apos;t know what those tiers will be exactly, I&apos;m figuring that out next. I do expect some blanket <strong>enterprise, site-wide licenses</strong> that hopefully makes everything simpler for everyone. I&apos;ve been on the other side of the table, getting licenses approved internally with clients, and I understand predictability and simplicity go a long way.</p><h3 id="thoughts-on-pricing">Thoughts on Pricing</h3><p>As for pricing, I don&apos;t have details yet, and probably won&apos;t until launch in the next couple months. Range-wise, it&apos;s hard to compare to other commercial or dual-licensed products out there, since I don&apos;t want to do any individual or per-seat license and that seems to be the norm. I am however keenly aware of how much tooling and library products cost as I have to pay for many of these myself.</p><p>But if I were to compare to the cost for a team of 10 or 50 or 100 for their IDEs, I would expect my commercial license price to be a fraction of that.</p><p>Thanks again to everyone that&apos;s reached out with kind words and support, and to the community for their patience while I figure things out.</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper and MediatR Going Commercial]]></title><description><![CDATA[<p>Yes, another one of &quot;those posts&quot;. But tl;dr:</p><p><strong>In order to ensure the long-term sustainability of my OSS projects, I will be commercializing AutoMapper and MediatR.</strong></p><p>I did not post this on April 1st for obvious reasons. But first a little background on how I got to</p>]]></description><link>https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/</link><guid isPermaLink="false">67ec40b82d65370001e55266</guid><category><![CDATA[AutoMapper]]></category><category><![CDATA[MediatR]]></category><category><![CDATA[OSS]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 02 Apr 2025 13:00:12 GMT</pubDate><content:encoded><![CDATA[<p>Yes, another one of &quot;those posts&quot;. But tl;dr:</p><p><strong>In order to ensure the long-term sustainability of my OSS projects, I will be commercializing AutoMapper and MediatR.</strong></p><p>I did not post this on April 1st for obvious reasons. But first a little background on how I got to this point.</p><h3 id="how-i-got-here">How I Got Here</h3><p>These two projects originated at my time at Headspring, a consulting company I worked at for over 12 years. About 5 years ago, in January 2020, I decided to strike off on my own and give solo consulting a try. Although it was a scary leap, it&apos;s been more rewarding than I could have possibly hoped for, in <em>almost</em> every area.</p><p>The area that it didn&apos;t work out well, and not at all intentionally, was OSS work:</p><figure class="kg-card kg-image-card"><img src="https://www.jimmybogard.com/content/images/2025/04/image-1.png" class="kg-image" alt loading="lazy" width="914" height="472" srcset="https://www.jimmybogard.com/content/images/size/w600/2025/04/image-1.png 600w, https://www.jimmybogard.com/content/images/2025/04/image-1.png 914w" sizes="(min-width: 720px) 720px"></figure><p>You can see exactly where my contributions cratered and flat-lined. And that&apos;s just commits&#x2014;issues, PRs, discussions, all my time dried up. This wasn&apos;t the intention but was a natural side effect of me focusing on my consulting business.</p><p>At Headspring, my time on OSS was directly encouraged and sponsored by them. I could use time between projects to invest back in existing OSS or new OSS, because it benefited the client, the company, and the employees (me and my coworkers).</p><p>With me leaving that company, and that company then selling to Accenture later that year, I had no direct major sponsor of my OSS work anymore. My free time was being spent growing and ensuring the success of my consulting company, which being solo, is...kinda important.</p><p>Taking time to see how things have been going on all fronts, I had a bit of a shock looking at my OSS work. I realized that model is not sustainable for the long-term success of these projects, which I still endorse and believe in. I need to be able to pay for my time to work on these projects, and get direct feedback from paying clients, like I had earlier at Headspring.</p><h3 id="what-will-this-look-like">What Will This Look Like?</h3><p>The short answer is &quot;I don&apos;t know exactly&quot;. I&apos;m working out those details now and will share them when I figure it out. I have lots of examples of what does and doesn&apos;t work well, at least from my perspective, as well as what I consider will work well for these projects.</p><p>Short term, nothing will change. I&apos;ll still be as (un)responsive on GitHub issues, and I just pushed out a couple releases of any existing work.</p><p>My goal is to be able to pay for the time to spend actually improving these projects, building out communities, helping more users, and in general, doing the things that people have asked me MANY times over the years that I should do, but I didn&apos;t, because it was not my job. OSS was/is/never will be a hobby for me. I want to change it to at least be part of my job and to fund real work.</p><p>I can&#x2019;t rely on donations, I don&apos;t want to make developers pay anything or do anything to punish/annoy them, and I certainly don&apos;t think it&apos;s Microsoft&apos;s job to &quot;pay me the money.&quot; Past that, I&apos;m still figuring it out.</p><h3 id="when-will-this-happen">When Will This Happen?</h3><p>I don&apos;t know, it&apos;s still just me that owns everything. It&apos;s still using my free time to sort it out, as my day job is still a consultant. But I plan to be open with this whole process. I&apos;m sure I&apos;ll surprise someone but the goal here is to be transparent.</p><p>Personally, I&apos;m both filled with excitement and dread&#x2014;doing these projects for so long has been incredibly rewarding, especially as this is code that came directly out of many, many long-lived production-deployed projects at Headspring. But I don&apos;t want these projects to wither and die on the vine, I want them to grow and evolve and thrive. But not just these projects&#x2014;I want ALL my OSS projects (Respawn etc.) to thrive. This is how it needs to happen.</p><h3 id="final-thanks">Final Thanks</h3><p>Thanks to all that have contributed over the years, and especially to <a href="https://www.linkedin.com/in/lbargaoanu/?ref=jimmybogard.com" rel="noreferrer">Lucian Bargaoanu</a> who really helped pick up the torch with AutoMapper after I more or less fell off the map. Also thanks to my GitHub sponsors, as many a pint has been purchased with your generous support. And finally thanks to the community, I never hoped anything I built would help anyone beyond my clients, coworkers, and company, but it&apos;s always nice to hear that it has.</p>]]></content:encoded></item><item><title><![CDATA[MediatR 12.5.0 Released]]></title><description><![CDATA[<p>I pushed out MediatR 12.5 today:</p><ul><li><a href="https://github.com/jbogard/MediatR/releases/tag/v12.5.0?ref=jimmybogard.com" rel="noreferrer">Release Notes</a></li><li><a href="https://www.nuget.org/packages/MediatR?ref=jimmybogard.com" rel="noreferrer">NuGet</a></li></ul><p>This is mainly a regular minor release with a couple extra interesting features:</p><ul><li><a href="https://github.com/jbogard/MediatR/pull/1065?ref=jimmybogard.com" rel="noreferrer">Adding convenience method to register open behaviors</a></li><li>Better cancellation token support (it&apos;s passed now everywhere including behaviors)</li></ul><p>And some other cleanup items as well. Enjoy!</p>]]></description><link>https://www.jimmybogard.com/mediatr-12-5-0-released/</link><guid isPermaLink="false">67ec0dc12d65370001e550b4</guid><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Tue, 01 Apr 2025 18:50:06 GMT</pubDate><content:encoded><![CDATA[<p>I pushed out MediatR 12.5 today:</p><ul><li><a href="https://github.com/jbogard/MediatR/releases/tag/v12.5.0?ref=jimmybogard.com" rel="noreferrer">Release Notes</a></li><li><a href="https://www.nuget.org/packages/MediatR?ref=jimmybogard.com" rel="noreferrer">NuGet</a></li></ul><p>This is mainly a regular minor release with a couple extra interesting features:</p><ul><li><a href="https://github.com/jbogard/MediatR/pull/1065?ref=jimmybogard.com" rel="noreferrer">Adding convenience method to register open behaviors</a></li><li>Better cancellation token support (it&apos;s passed now everywhere including behaviors)</li></ul><p>And some other cleanup items as well. Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[AutoMapper 14.0 Released]]></title><description><![CDATA[<p>I pushed out version 14.0 (!) of AutoMapper over the weekend:</p><ul><li><a href="https://github.com/AutoMapper/AutoMapper/releases/tag/v14.0.0?ref=jimmybogard.com" rel="noreferrer">Release notes</a></li><li><a href="https://www.nuget.org/packages/automapper/?ref=jimmybogard.com" rel="noreferrer">NuGet</a></li></ul><p>This release targets .NET 8 (up from .NET 6 from the previous release). It&apos;s mainly a bug fix release, with some quality-of-life improvements in configuration validation where we gather up all the possible validation</p>]]></description><link>https://www.jimmybogard.com/automapper-14-0-released/</link><guid isPermaLink="false">67b5ddd3b4a0a20001dad589</guid><category><![CDATA[AutoMapper]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 19 Feb 2025 13:41:24 GMT</pubDate><content:encoded><![CDATA[<p>I pushed out version 14.0 (!) of AutoMapper over the weekend:</p><ul><li><a href="https://github.com/AutoMapper/AutoMapper/releases/tag/v14.0.0?ref=jimmybogard.com" rel="noreferrer">Release notes</a></li><li><a href="https://www.nuget.org/packages/automapper/?ref=jimmybogard.com" rel="noreferrer">NuGet</a></li></ul><p>This release targets .NET 8 (up from .NET 6 from the previous release). It&apos;s mainly a bug fix release, with some quality-of-life improvements in configuration validation where we gather up all the possible validation errors before reporting them in an aggregate exception.</p><p>Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[Integrating the Particular Service Platform with Aspire]]></title><description><![CDATA[<p>I&apos;ve been playing around with <a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview?ref=jimmybogard.com" rel="noreferrer">Aspire</a> for a bit mainly to understand &quot;is this a thing I should care about?&quot; and part of what I wanted to do is take a complex &quot;hello world&quot; distributed system and convert it to Aspire. Along the way,</p>]]></description><link>https://www.jimmybogard.com/integrating-the-particular-service-platform-with-aspire/</link><guid isPermaLink="false">66f30b5c244a6a0001eb7f28</guid><category><![CDATA[NServiceBus]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Tue, 24 Sep 2024 22:48:57 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve been playing around with <a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview?ref=jimmybogard.com" rel="noreferrer">Aspire</a> for a bit mainly to understand &quot;is this a thing I should care about?&quot; and part of what I wanted to do is take a complex &quot;hello world&quot; distributed system and convert it to Aspire. Along the way, Particular Software also released container support for their <a href="https://particular.net/service-platform?ref=jimmybogard.com" rel="noreferrer">Service Platform</a>, so it also seemed like a good opportunity to try it out.</p><p>I&apos;ll follow up in another post about Aspire impressions, but the NServiceBus part was actually relatively simple. Many Aspire integrations have some kind of 1st-party support where you can do things like:</p><pre><code class="language-csharp">var rmqPassword = builder.AddParameter(&quot;messaging-password&quot;);
var dbPassword = builder.AddParameter(&quot;db-password&quot;);

var broker = builder.AddRabbitMQ(name: &quot;broker&quot;, password: rmqPassword, port: 5672)
    .WithDataVolume()
    .WithManagementPlugin()
    .WithEndpoint(&quot;management&quot;, e =&gt; e.Port = 15672)
    .WithHealthCheck();
var mongo = builder.AddMongoDB(&quot;mongo&quot;);
var sql = builder.AddSqlServer(&quot;sql&quot;, password: dbPassword)
    .WithHealthCheck()
    .WithDataVolume()
    .AddDatabase(&quot;sqldata&quot;);</code></pre><p>And now my system has RabbitMQ, MongoDB, and SQL Server up and running in containers. There&apos;s a lot of stock configuration going on behind <code>AddSqlServer</code> and similar methods but we don&apos;t <em>have</em> to use those convenience methods if we don&apos;t want to.</p><p>The overall Service Platform architecture looks something like:</p><figure class="kg-card kg-image-card"><img src="https://www.jimmybogard.com/content/images/2024/09/image-1.png" class="kg-image" alt loading="lazy" width="2000" height="1136" srcset="https://www.jimmybogard.com/content/images/size/w600/2024/09/image-1.png 600w, https://www.jimmybogard.com/content/images/size/w1000/2024/09/image-1.png 1000w, https://www.jimmybogard.com/content/images/size/w1600/2024/09/image-1.png 1600w, https://www.jimmybogard.com/content/images/2024/09/image-1.png 2352w" sizes="(min-width: 720px) 720px"></figure><p>The &quot;instances&quot; here are running containers that we need to configure in Aspire. On top of that, we might also want to have Service Pulse (another container) and Service Insight (a Windows-only WPF app) running, and these all require extra configuration. Also, the Error and Audit instances use RavenDB as their backing store but Particular also has an image there. The <a href="https://hub.docker.com/r/particular/servicecontrol?ref=jimmybogard.com" rel="noreferrer">Docker Hub site</a> has links to docs on both the instance and containers.</p><p>First up, we need to provide our license to the running containers as raw text in an environment variable, so we&apos;ll just read our license (this is just for local development):</p><pre><code class="language-csharp">var license = File.ReadAllText(                                              
    Path.Combine(                                                            
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        &quot;ParticularSoftware&quot;,                                                
        &quot;license.xml&quot;));                                                     </code></pre><p>Next, we need our RavenDB instance. There&apos;s a special image from Particular, so we&apos;ll use the <code>AddContainer</code> method to add our custom image to our Aspire distributed application:</p><pre><code class="language-csharp">builder                                                                             
    .AddContainer(&quot;servicecontroldb&quot;, &quot;particular/servicecontrol-ravendb&quot;, &quot;latest&quot;)
    .WithBindMount(&quot;AppHost-servicecontroldb-data&quot;, &quot;/opt/RavenDB/Server/RavenData&quot;)
    .WithEndpoint(8080, 8080);                                                      </code></pre><p>The container docs say that we must mount a persistent volume to that path, so we use the <code>WithBindMount</code> method to mount the volume following the <a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/persist-data-volumes?ref=jimmybogard.com#understand-volumes" rel="noreferrer">Aspire docs</a>.</p><p>Next up are the Particular containers!</p><h3 id="setting-up-the-service-control-error-instance">Setting up the Service Control Error instance</h3><p>From the <a href="https://docs.particular.net/servicecontrol/servicecontrol-instances/deployment/containers?ref=jimmybogard.com" rel="noreferrer">Particular docs</a>, we see that we need to supply configuration for:</p><ul><li>Transport type (RabbitMQ, Azure Service Bus, etc.)</li><li>Connection string to the transport</li><li>Connection string to the Raven DB instance</li><li>Audit instance URLs</li><li>License</li></ul><p>Plus port mapping. Pretty quickly I ran into a few challenges:</p><ul><li>The Service Control image can start before RabbitMQ is &quot;ready&quot;, resulting in connection failures</li><li>Service Insight, the WPF app, is Windows only so I need to connect to Service Control from a VM</li></ul><p>The base configuration is fairly straightforward, we specify the container and image, with environment variables:</p><pre><code class="language-csharp">builder                                                                                             
    .AddContainer(&quot;servicecontrol&quot;, &quot;particular/servicecontrol&quot;)                                    
    .WithEnvironment(&quot;TransportType&quot;, &quot;RabbitMQ.QuorumConventionalRouting&quot;)                         
    .WithEnvironment(&quot;ConnectionString&quot;, &quot;host=host.docker.internal&quot;)                               
    .WithEnvironment(&quot;RavenDB_ConnectionString&quot;, &quot;http://host.docker.internal:8080&quot;)                
    .WithEnvironment(&quot;RemoteInstances&quot;, &quot;[{\&quot;api_uri\&quot;:\&quot;http://host.docker.internal:44444/api\&quot;}]&quot;)
    .WithEnvironment(&quot;PARTICULARSOFTWARE_LICENSE&quot;, license)                                         
    .WithArgs(&quot;--setup-and-run&quot;)                                                                    </code></pre><p>But the other two challenges are a bit harder to deal with. There is no built-in way in Aspire to &quot;wait&quot; for other resources to start. This isn&apos;t new to Aspire - in the past we had to write custom hooks in Docker Compose to wait for our dependencies&apos; health checks to come back. The extensibility is there to do such a thing, so I found an <a href="https://nikiforovall.github.io/dotnet/aspire/2024/06/28/startup-dependencies-aspire.html?ref=jimmybogard.com" rel="noreferrer">extension to do just that</a>.</p><p>The second problem was...a long slog to figure out. It&apos;s possible to have a Parallels VM be able to communicate with Docker containers <a href="https://samestuffdifferentday.net/2024/08/02/working-in-parallels-and-docker-on-host/?ref=jimmybogard.com" rel="noreferrer">running in the Mac host</a>. However, I could <strong>not</strong> get this to work with Aspire. After doing side-by-side comparisons between container manifests running inside/outside of Aspire, I found the culprit:</p><pre><code class="language-diff">&quot;PortBindings&quot;: {
	&quot;8080/tcp&quot;: [
		{
-			&quot;HostIp&quot;: &quot;&quot;,
+			&quot;HostIp&quot;: &quot;127.0.0.1&quot;,
			&quot;HostPort&quot;: &quot;8000&quot;
		}
	]
},
</code></pre><p>With the Docker CLI, doing <code>-p 8080:8000</code> does not set the host IP. Aspire does however, which means I can only access this container via <code>localhost</code>. Not ideal because my Windows VM is definitely not able to access that. Instead of using <code>WithEndpoint</code> or similar, I have to drop down to container runtime args:</p><pre><code class="language-csharp">.WithContainerRuntimeArgs(&quot;-p&quot;, &quot;33333:33333&quot;)
.WaitFor(rabbitMqResource);                   </code></pre><p>Now my Service Control instance is up and running!</p><h3 id="setting-up-service-control-audit-monitoring-and-service-pulse">Setting up Service Control Audit, Monitoring, and Service Pulse</h3><p>Following our previous example, we can finish out our configuration for the other container instances:</p><pre><code class="language-csharp">builder                                                                              
    .AddContainer(&quot;servicecontrolaudit&quot;, &quot;particular/servicecontrol-audit&quot;)          
    .WithEnvironment(&quot;TransportType&quot;, &quot;RabbitMQ.QuorumConventionalRouting&quot;)          
    .WithEnvironment(&quot;ConnectionString&quot;, &quot;host=host.docker.internal&quot;)                
    .WithEnvironment(&quot;RavenDB_ConnectionString&quot;, &quot;http://host.docker.internal:8080&quot;) 
    .WithEnvironment(&quot;PARTICULARSOFTWARE_LICENSE&quot;, license)                          
    .WithArgs(&quot;--setup-and-run&quot;)                                                     
    .WithEndpoint(44444, 44444)                                                      
    .WaitFor(rabbitMqResource);                                                      
                                                                                     
builder                                                                              
    .AddContainer(&quot;servicecontrolmonitoring&quot;, &quot;particular/servicecontrol-monitoring&quot;)
    .WithEnvironment(&quot;TransportType&quot;, &quot;RabbitMQ.QuorumConventionalRouting&quot;)          
    .WithEnvironment(&quot;ConnectionString&quot;, &quot;host=host.docker.internal&quot;)                
    .WithEnvironment(&quot;PARTICULARSOFTWARE_LICENSE&quot;, license)                          
    .WithArgs(&quot;--setup-and-run&quot;)                                                     
    .WithEndpoint(33633, 33633)                                                      
    .WaitFor(rabbitMqResource);                                                      
                                                                                     
builder                                                                              
    .AddContainer(&quot;servicepulse&quot;, &quot;particular/servicepulse&quot;)                         
    .WithEnvironment(&quot;SERVICECONTROL_URL&quot;, &quot;http://host.docker.internal:33333&quot;)      
    .WithEnvironment(&quot;MONITORING_URL&quot;, &quot;http://host.docker.internal:33633&quot;)          
    .WithEnvironment(&quot;PARTICULARSOFTWARE_LICENSE&quot;, license)                          
    .WithEndpoint(9090, 9090)                                                        
    .WaitFor(rabbitMqResource);                                                      </code></pre><p>With all this in place in my Service Pulse instance is up and running:</p><figure class="kg-card kg-image-card"><img src="https://www.jimmybogard.com/content/images/2024/09/image-2.png" class="kg-image" alt loading="lazy" width="1964" height="886" srcset="https://www.jimmybogard.com/content/images/size/w600/2024/09/image-2.png 600w, https://www.jimmybogard.com/content/images/size/w1000/2024/09/image-2.png 1000w, https://www.jimmybogard.com/content/images/size/w1600/2024/09/image-2.png 1600w, https://www.jimmybogard.com/content/images/2024/09/image-2.png 1964w" sizes="(min-width: 720px) 720px"></figure><p>And on the Service Insight side, I had to do the Parallels trick of using my hosts file to create a special &quot;localhost.mac&quot; entry to point to the Mac host:</p><pre><code>10.211.55.2 localhost.mac</code></pre><p>With this in place, I can configure Service Insight in Windows to connect to the Docker Service Pulse instance running in Docker on the Mac:</p><figure class="kg-card kg-image-card"><img src="https://www.jimmybogard.com/content/images/2024/09/image-3.png" class="kg-image" alt loading="lazy" width="662" height="264" srcset="https://www.jimmybogard.com/content/images/size/w600/2024/09/image-3.png 600w, https://www.jimmybogard.com/content/images/2024/09/image-3.png 662w"></figure><p>All my NServiceBus messages and traces now show up just fine:</p><figure class="kg-card kg-image-card"><img src="https://www.jimmybogard.com/content/images/2024/09/image-4.png" class="kg-image" alt loading="lazy" width="2000" height="1483" srcset="https://www.jimmybogard.com/content/images/size/w600/2024/09/image-4.png 600w, https://www.jimmybogard.com/content/images/size/w1000/2024/09/image-4.png 1000w, https://www.jimmybogard.com/content/images/size/w1600/2024/09/image-4.png 1600w, https://www.jimmybogard.com/content/images/2024/09/image-4.png 2266w" sizes="(min-width: 720px) 720px"></figure><p>Most of the work I had to do was not really Aspire-related, but just configuring Aspire to pass in the appropriate configuration to the containers. You can find the full code to my configuration here:</p><p><a href="https://github.com/jbogard/nsb-diagnostics-poc?ref=jimmybogard.com" rel="noreferrer">Code Example</a></p><p>Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[Tales from the .NET Migration Trenches - Turning Off the Lights]]></title><description><![CDATA[<p>Posts in this series:</p><ul><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches/" rel="noreferrer">Intro</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-catalog" rel="noreferrer">Cataloging</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-empty-proxy" rel="noreferrer">Empty Proxy</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-shared-library" rel="noreferrer">Shared Library</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-controller" rel="noreferrer">Our First Controller</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-migrating-business-logic" rel="noreferrer">Migrating Initial Business Logic</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-views" rel="noreferrer">Our First Views</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-session-state/" rel="noreferrer">Session State</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-hangfire/" rel="noreferrer">Hangfire</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-authentication/" rel="noreferrer">Authentication</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-middleware/" rel="noreferrer">Middleware</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-turning-off-the-lights/" rel="noreferrer">Turning Off the Lights</a></li></ul><p>In the last post, we looked at migrating our middleware, which we tackle in an as-needed basis. When a controller needs</p>]]></description><link>https://www.jimmybogard.com/tales-from-the-net-migration-trenches-turning-off-the-lights/</link><guid isPermaLink="false">66c64f88107b28000140eabd</guid><category><![CDATA[ASP.NET Core]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Thu, 05 Sep 2024 15:25:16 GMT</pubDate><content:encoded><![CDATA[<p>Posts in this series:</p><ul><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches/" rel="noreferrer">Intro</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-catalog" rel="noreferrer">Cataloging</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-empty-proxy" rel="noreferrer">Empty Proxy</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-shared-library" rel="noreferrer">Shared Library</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-controller" rel="noreferrer">Our First Controller</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-migrating-business-logic" rel="noreferrer">Migrating Initial Business Logic</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-views" rel="noreferrer">Our First Views</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-session-state/" rel="noreferrer">Session State</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-hangfire/" rel="noreferrer">Hangfire</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-authentication/" rel="noreferrer">Authentication</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-middleware/" rel="noreferrer">Middleware</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-turning-off-the-lights/" rel="noreferrer">Turning Off the Lights</a></li></ul><p>In the last post, we looked at migrating our middleware, which we tackle in an as-needed basis. When a controller needs middleware to be migrated, we migrate that middleware over. If the entire app needs the middleware, it needs to come rather early.</p><p>Once we migrate much of our middleware over, it becomes much less work to incrementally migrate individual controllers and their subsequent actions/pages over. I won&apos;t go into deep detail into this part - mostly it&apos;s fixing namespaces, adjusting features (such as converting child actions into view components), but it can go <em>quite</em> fast. On recent teams I was working with, we migrated easily a dozen controllers a week amongst 3-4 developers. At this point, the bottleneck wasn&apos;t the conversion, but testing to make sure the pages still worked correctly</p><p>It&apos;s essentially testing the entire application, one page at a time, so hopefully you&apos;ve got some regression tests in some form or fashion. I&apos;m not skipping the incremental controller migration because it&apos;s not interesting - it&apos;s just because our teams really didn&apos;t encounter many challenges there. There will be <em>something</em> that comes up, there always is, but just the controller/action/view part is not too terrible.</p><p>But in this post I wanted to focus on getting to the end - what do we do once we&apos;ve migrated everything but authentication? When there&apos;s just one controller left, we&apos;re now OK to proceed with migrating the last pieces over and &quot;turning off the lights&quot; on the .NET 4.x application.</p><h3 id="migrating-last-features">Migrating Last Features</h3><p>The last (or next-to-last) migration typically:</p><ul><li>Migrates the last controller, usually authentication</li><li>Turns off proxying and all remote app features</li></ul><p>You don&apos;t necessarily need to split this into two separate units of work/deployments, as once you&apos;ve migrated the last set of requests you can migrate all final features from the .NET Framework application. If the last controller is authentication, we&apos;ll also need to remove remote authentication. Our current web adapter configuration before final migration is:</p><pre><code class="language-csharp">builder.Services.AddSystemWebAdapters()
    .AddJsonSessionSerializer(options =&gt;
    {
        options.RegisterKey&lt;string&gt;(&quot;FavoriteInstructor&quot;);
    })
    .AddRemoteAppClient(options =&gt;
    {
        // Provide the URL for the remote app that has enabled session querying
        options.RemoteAppUrl = new(builder.Configuration[&quot;ProxyTo&quot;]);

        // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
        options.ApiKey = builder.Configuration[&quot;RemoteAppApiKey&quot;];
    })
    .AddAuthenticationClient(true)
    .AddSessionClient();
builder.Services.AddHttpForwarder();</code></pre><p>With middleware:</p><pre><code class="language-csharp">app.UseSystemWebAdapters();

app.MapDefaultControllerRoute();
app.MapForwarder(&quot;/{**catch-all}&quot;, app.Configuration[&quot;ProxyTo&quot;]).Add(
    static builder =&gt; ((RouteEndpointBuilder)builder).Order = int.MaxValue);</code></pre><p>Along with migrating the authentication piece and all related middleware, we&apos;ll remove the above from our application startup, as well as the package references to all the proxy and System.WebAdapters packages. Once that&apos;s complete, our .NET application should now handle <em>all</em> requests. There might still be a few extra features to enable in .NET 8, such as Session:</p><pre><code class="language-csharp">builder.Services.AddSession();

// later

app.UseSession();</code></pre><p>With all that complete, our .NET 8 application should now serve all requests and host all features needed to run our entire system.</p><h3 id="turning-off-the-lights">Turning off the lights</h3><p>While our .NET 8 application may now be &quot;complete&quot;, we&apos;re not quite done yet. In my typical last phase we will:</p><ul><li>Deploy the completed .NET 8 application to production</li><li>Monitor for any errors and any activity from the .NET 4.8 application</li><li>Adjust our .NET 8 application as necessary</li></ul><p>If we don&apos;t see any issues, then the final <em>final</em> cleanup is:</p><ul><li>Remove all .NET 4.8 code from the repository</li><li>Remove any shims to bridge from .NET 8 to .NET 4.8</li><li>Remove all .NET 4.8 application pipelines and deployments</li><li>Remove all .NET 4.8 production resources</li></ul><p>And we should end with something like:</p><figure class="kg-card kg-image-card"><a href="https://x.com/jbogard/status/1813667695289933872?ref=jimmybogard.com"><img src="https://www.jimmybogard.com/content/images/2024/09/image.png" class="kg-image" alt loading="lazy" width="1172" height="664" srcset="https://www.jimmybogard.com/content/images/size/w600/2024/09/image.png 600w, https://www.jimmybogard.com/content/images/size/w1000/2024/09/image.png 1000w, https://www.jimmybogard.com/content/images/2024/09/image.png 1172w" sizes="(min-width: 720px) 720px"></a></figure><p>So what&apos;s next? There&apos;s still probably quite a bit to do to &quot;.NET-8-ify&quot; our existing system - all those architectural improvements we skipped in order to fast track migration. But most important - celebrate!</p>]]></content:encoded></item><item><title><![CDATA[Upcoming Training on DDD with Vertical Slice Architecture in Munich]]></title><description><![CDATA[<p>I&apos;ve got another training event coming up focusing on Domain-Driven Design with Vertical Slice Architecture in Munich on October 21-23rd.</p><p>A little different than the previous times I&apos;ve given this course is an option for either a 2-day or 3-day version. I had received feedback that</p>]]></description><link>https://www.jimmybogard.com/upcoming-training-on-ddd-with-vertical-slice-architecture-in-munich/</link><guid isPermaLink="false">66cef86704d451000113fc6f</guid><category><![CDATA[Training]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Wed, 28 Aug 2024 10:28:04 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve got another training event coming up focusing on Domain-Driven Design with Vertical Slice Architecture in Munich on October 21-23rd.</p><p>A little different than the previous times I&apos;ve given this course is an option for either a 2-day or 3-day version. I had received feedback that folks were also interested in larger-scale design concepts such as bounded contexts, messaging, integration patterns, microservices, and modular monoliths. So I&apos;ve included a 3rd day that covers these topics, where we look at encapsulation and cohesion at larger and larger scopes.</p><p>We&apos;ll cover:</p><ul><li>Refactoring an existing system to leverage Vertical Slice Architecture</li><li>Applying Domain-Driven Design techniques to model complex business needs</li><li>Communication between slices</li><li>Exploring Validation and Testing (and other cross-cutting concerns) using Vertical Slice Architecture</li><li>Examining various design patterns, code smells, and refactoring techniques</li><li>Implementing the Vertical Slice Architectural pattern in various enterprise application scenarios (minimal APIs, Blazor, Web APIs, etc.)</li></ul><p>And on the final day:</p><ul><li>Service boundaries and bounded contexts</li><li>Communication between bounded contexts</li><li>Microservices and modular monoliths</li><li>Studying distributed systems patterns, tools, and libraries such as NServiceBus</li></ul><p>The course pulls together my experiences building such systems for nearly 20 years now. And if you can&apos;t make the course during the day, I&apos;m also hosting a networking event during the evening where you can meet myself and the other attendees and ask me questions. I hope to see you there!</p><p><a href="https://my.weezevent.com/domain-driven-design-with-vertical-slice-architecture-1?ref=jimmybogard.com" rel="noreferrer">Register Now</a></p>]]></content:encoded></item><item><title><![CDATA[Tales from the .NET Migration Trenches - Middleware]]></title><description><![CDATA[<p>Posts in this series:</p><ul><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches/" rel="noreferrer">Intro</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-catalog" rel="noreferrer">Cataloging</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-empty-proxy" rel="noreferrer">Empty Proxy</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-shared-library" rel="noreferrer">Shared Library</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-controller" rel="noreferrer">Our First Controller</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-migrating-business-logic" rel="noreferrer">Migrating Initial Business Logic</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-views" rel="noreferrer">Our First Views</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-session-state/" rel="noreferrer">Session State</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-hangfire/" rel="noreferrer">Hangfire</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-authentication/" rel="noreferrer">Authentication</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-middleware/" rel="noreferrer">Middleware</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-turning-off-the-lights/" rel="noreferrer">Turning Off the Lights</a></li></ul><p>In the last post, we looked at tackling probably the most important pieces of middleware - authentication. But many ASP.NET</p>]]></description><link>https://www.jimmybogard.com/tales-from-the-net-migration-trenches-middleware/</link><guid isPermaLink="false">66b13fa240108700019ba682</guid><category><![CDATA[ASP.NET Core]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Tue, 06 Aug 2024 15:46:39 GMT</pubDate><content:encoded><![CDATA[<p>Posts in this series:</p><ul><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches/" rel="noreferrer">Intro</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-catalog" rel="noreferrer">Cataloging</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-empty-proxy" rel="noreferrer">Empty Proxy</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-shared-library" rel="noreferrer">Shared Library</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-controller" rel="noreferrer">Our First Controller</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-migrating-business-logic" rel="noreferrer">Migrating Initial Business Logic</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-our-first-views" rel="noreferrer">Our First Views</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-session-state/" rel="noreferrer">Session State</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-hangfire/" rel="noreferrer">Hangfire</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-authentication/" rel="noreferrer">Authentication</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-middleware/" rel="noreferrer">Middleware</a></li><li><a href="https://www.jimmybogard.com/tales-from-the-net-migration-trenches-turning-off-the-lights/" rel="noreferrer">Turning Off the Lights</a></li></ul><p>In the last post, we looked at tackling probably the most important pieces of middleware - authentication. But many ASP.NET MVC 5 applications will have lots of middleware, but not all of the middleware should be migrated without some analysis on whether or not that middleware is actually needed anymore.</p><p>This part is entirely dependent on your application - you might have little to no middleware, or lots. Middleware can also exist in a number of different places:</p><ul><li>Web.config (you WILL forget this)</li><li>Global.asax (probably calling into other classes with the middleware configuration)</li><li>OWIN Startup</li></ul><p>When choosing the first controller to migrate, I&apos;m also looking at which controllers have the least amount of middleware, just to minimize the heavy first lift.</p><p>Let&apos;s look at our various middleware, and see what makes sense to move over, starting with our <code>web.config</code>.</p><h3 id="migrating-webconfig">Migrating Web.Config</h3><p>I think I forget the web.config middleware mainly because I&apos;ve tried to burn most things ASP.NET from my brain. But we&apos;ll find lots of important hosting configuration settings in our web.config, from custom middleware to error handling, application configuration, server configuration and more. Luckily for us, nearly all out-of-the-box configuration has a direct analog in Kestrel. We mostly need to worry about anything custom here. My sample app doesn&apos;t have a lot going on:</p><pre><code class="language-xml">&lt;system.web&gt;                                                                                        
  &lt;compilation debug=&quot;true&quot; targetFramework=&quot;4.8.1&quot; /&gt;                                              
  &lt;httpRuntime targetFramework=&quot;4.8.1&quot; /&gt;                                                           
  &lt;customErrors mode=&quot;RemoteOnly&quot; redirectMode=&quot;ResponseRewrite&quot;&gt;                                   
    &lt;error statusCode=&quot;404&quot; redirect=&quot;/404Error.aspx&quot; /&gt;                                            
  &lt;/customErrors&gt;                                                                                   
  &lt;!-- Glimpse: This can be commented in to add additional data to the Trace tab when using WebForms
      &lt;trace writeToDiagnosticsTrace=&quot;true&quot; enabled=&quot;true&quot; pageOutput=&quot;false&quot;/&gt; --&gt;                 
  &lt;httpModules&gt;                                                                                     
    &lt;add name=&quot;Glimpse&quot; type=&quot;Glimpse.AspNet.HttpModule, Glimpse.AspNet&quot; /&gt;                         
  &lt;/httpModules&gt;                                                                                    
  &lt;httpHandlers&gt;                                                                                    
    &lt;add path=&quot;glimpse.axd&quot; verb=&quot;GET&quot; type=&quot;Glimpse.AspNet.HttpHandler, Glimpse.AspNet&quot; /&gt;         
  &lt;/httpHandlers&gt;                                                                                   
&lt;/system.web&gt;                                                                                       </code></pre><p>We only have one set of custom modules/handlers and it&apos;s the now-dead (and much missed) Glimpse project. In the rest of the configuration, we only see custom errors redirecting to an .ASPX page, which we can easily port over using <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0&amp;ref=jimmybogard.com" rel="noreferrer">custom errors in ASP.NET Core</a>. Otherwise there&apos;s not much going on here.</p><p>In a typical application, the things I&apos;ve needed to migrate over might include such settings as:</p><ul><li>Authentication</li><li>Authorization</li><li>Cookies</li><li>Session state</li><li>Data protection</li><li>Static files</li><li>HTTP request methods</li><li>Initialization</li><li>Custom headers</li><li>Caching</li></ul><p>Each of these has some analog in ASP.NET Core Kestrel configuration. But luckily for us, we don&apos;t have any custom handlers/modules to worry about, only porting ASP.NET runtime features to ASP.NET Core.</p><h3 id="aspnet-mvc-5-middleware">ASP.NET MVC 5 Middleware</h3><p>Next up is ASP.NET MVC 5 middleware, which is typically set up in the <code>Global.asax.cs</code> file, something like:</p><pre><code class="language-csharp">AreaRegistration.RegisterAllAreas();                      
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);            
BundleConfig.RegisterBundles(BundleTable.Bundles);        </code></pre><p>The global filters registered are:</p><pre><code class="language-csharp">public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{                                                                       
    filters.Add(new HandleErrorAttribute());                            
    filters.Add(new ValidatorActionFilter());                           
    filters.Add(new MvcTransactionFilter());                            
}                                                                       </code></pre><p>The first filter here is a built-in one from ASP.NET MVC to provide global error handling (with no extra configuration), but the second two are custom. The first custom filter provides some customization around handling validation errors and providing a common error result back to the UI:</p><pre><code class="language-csharp">public void OnActionExecuting(ActionExecutingContext filterContext)                                   
{                                                                                                     
    if (!filterContext.Controller.ViewData.ModelState.IsValid)                                        
    {                                                                                                 
        if (filterContext.HttpContext.Request.HttpMethod == &quot;GET&quot;)                                    
        {                                                                                             
            var result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);                         
            filterContext.Result = result;                                                            
        }                                                                                             
        else                                                                                          
        {                                                                                             
            var result = new ContentResult();                                                         
            string content = JsonConvert.SerializeObject(filterContext.Controller.ViewData.ModelState,
                new JsonSerializerSettings                                                            
                {                                                                                     
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore                              
                });                                                                                   
            result.Content = content;                                                                 
            result.ContentType = &quot;application/json&quot;;                                                  
                                                                                                      
            filterContext.HttpContext.Response.StatusCode = 400;                                      
            filterContext.Result = result;                                                            
        }                                                                                             
    }                                                                                                 
}                                                                                                     </code></pre><p>The front end does still need this, so we want to port this over. The second filter provides automatic transaction handling:</p><pre><code class="language-csharp">public class MvcTransactionFilter : ActionFilterAttribute                                              
{                                                                                                      
    public override void OnActionExecuting(ActionExecutingContext filterContext)                       
    {                                                                                                  
        // Logger.Instance.Verbose(&quot;MvcTransactionFilter::OnActionExecuting&quot;);                         
        var context = StructuremapMvc.ParentScope.CurrentNestedContainer.GetInstance&lt;SchoolContext&gt;(); 
        context.BeginTransaction();                                                                    
    }                                                                                                  
                                                                                                       
    public override void OnActionExecuted(ActionExecutedContext filterContext)                         
    {                                                                                                  
        // Logger.Instance.Verbose(&quot;MvcTransactionFilter::OnActionExecuted&quot;);                          
        var instance = StructuremapMvc.ParentScope.CurrentNestedContainer.GetInstance&lt;SchoolContext&gt;();
        instance.CloseTransaction(filterContext.Exception);                                            
    }                                                                                                  
}                                                                                                      </code></pre><p>I might not do automatic transactions like this in a normal project but because the application code expects it, we&apos;ll need to migrate this over as well. The transaction filter is interesting because it highlights the shortcomings of ASP.NET MVC 5&apos;s dependency injection capabilities - namely there wasn&apos;t anything built in for filters.  Instead of migrating this filter as-is, we need to translate to the equivalent ASP.NET Core filter:</p><pre><code class="language-csharp">public class DbContextTransactionFilter : IAsyncActionFilter                                              
{                                                                                                         
    private readonly SchoolContext _dbContext;                                                            
                                                                                                          
    public DbContextTransactionFilter(SchoolContext dbContext)                                            
    {                                                                                                     
        _dbContext = dbContext;                                                                           
    }                                                                                                     
                                                                                                          
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {                                                                                                     
        try                                                                                               
        {                                                                                                 
            _dbContext.BeginTransaction();                                                                
                                                                                                          
            var actionExecuted = await next();                                                            
            if (actionExecuted.Exception != null &amp;&amp; !actionExecuted.ExceptionHandled)                     
            {                                                                                             
                _dbContext.CloseTransaction(actionExecuted.Exception);                                    
                                                                                                          
            }                                                                                             
            else                                                                                          
            {                                                                                             
                _dbContext.CloseTransaction();                                                            
                                                                                                          
            }                                                                                             
        }                                                                                                 
        catch (Exception ex)                                                                              
        {                                                                                                 
            _dbContext.CloseTransaction(ex);                                                              
            throw;                                                                                        
        }                                                                                                 
    }                                                                                                     
}                                                                                                         </code></pre><p>And we register our filter:</p><pre><code class="language-csharp">builder.Services.AddControllersWithViews(opt =&gt;
{
    opt.Filters.Add&lt;DbContextTransactionFilter&gt;();
});</code></pre><p>Now our filter will have its <code>DbContext</code> injected instead of going out to a custom extension to mimic per-request service lifetimes.</p><p>Finally, let&apos;s look at the OWIN middleware.</p><h3 id="owin-middleware">OWIN Middleware</h3><p>OWIN middleware can be found in classes with the <code>OwinStartup</code> attribute configured for them. Usually this is a &quot;Startup&quot; class but it could be anything. In my sample app, we have:</p><pre><code class="language-csharp">[assembly: OwinStartup(typeof(Startup))]
namespace ContosoUniversity
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();

            GlobalConfiguration.Configuration
                .UseSqlServerStorage(&quot;SchoolContext&quot;)
                .UseStructureMapActivator(IoC.Container)
                ;


            app.UseHangfireDashboard();
            app.UseHangfireServer(new BackgroundJobServerOptions
            {
                Queues = new[] { Queues.Default }
            });

            ConfigureAuth(app);
        }
    }
}</code></pre><p>Basically, it&apos;s:</p><ul><li>SignalR</li><li>Hangfire</li><li>Authentication</li></ul><p>Authentication might differ slightly than the ASP.NET authentication, so we&apos;ll want to port settings there. SignalR and Hangfire can be dealt with individually, but otherwise we don&apos;t have any custom OWIN middleware. This is fairly typical unless your application wholly relies on OWIN instead of say, IIS.</p><p>Middleware isn&apos;t the most exciting code to port over, but critical for ensuring our new application preserves the existing behavior of the .NET Framework application.</p><p>In our last post, we&apos;ll cover finishing up our migration and &quot;turning off the lights&quot;.</p>]]></content:encoded></item><item><title><![CDATA[Vertical Slice Architecture Training Course in July in the Netherlands]]></title><description><![CDATA[<p>The last training course in Zurich was a success, in that no laptops were harmed. I think. I put a poll out on where I should do the training next and quite a few folks suggested the Netherlands. I&apos;m happy to announce that the next VSA course will</p>]]></description><link>https://www.jimmybogard.com/vertical-slice-architecture-training-course-in-july/</link><guid isPermaLink="false">6626e04f5e4cd90001257cb0</guid><category><![CDATA[Training]]></category><dc:creator><![CDATA[Jimmy Bogard]]></dc:creator><pubDate>Mon, 22 Apr 2024 22:15:00 GMT</pubDate><content:encoded><![CDATA[<p>The last training course in Zurich was a success, in that no laptops were harmed. I think. I put a poll out on where I should do the training next and quite a few folks suggested the Netherlands. I&apos;m happy to announce that the next VSA course will be in the Netherlands on July 17-18th. </p><p>This course approaches this topic from the perspective of refactoring an existing system to this architecture. We also look at larger and larger boundaries of cohesion, from applications to services to systems. I&apos;m also doing something new, a Q&amp;A at a pub where you can ask questions while we share authentic Dutch beer (Heineken).</p><p>More details here:</p><p><a href="https://codeartify.com/events/jimmy-in-netherlands?ref=jimmybogard.com" rel="noreferrer">Vertical Slice Architecture Training</a></p><p>Hope to see you there!</p>]]></content:encoded></item></channel></rss>